文章目录
一.前言
自定义View的实现需要我们对View的层次结构,事件分发机制和View的工作原理等知识有较好的掌握,具体的可参看如下的这些博客。
Android View(一)——View的基础知识
Android View(二)——View的事件分发机制
Android View(三)——View的滑动冲突
Android View(四)——View的工作原理
二.自定义View的分类
1.继承View重写onDraw方法
主要用来实现一些不规则的效果,这种效果不方便用布局组合的方式达到,通常需要静态或者动态的显示一些不规则的图形,这种需要绘制的需要自己支持wrap_content,并支持padding。
2.继承ViewGroup派生出特殊的Layout
这种方式主要用于实现自定义布局,当某种效果看起来很像几种Veiw组合在一起的时候,可以采取这种方式来实现。这种方式复杂些,需要合适的处理ViewGroup的测量布局这两个过程,并同时处理子元素的测量和布局过程。
3.继承特定的View(比如TextView)
这种方法比较常见,一般用于扩展已有的View功能,比如TextView,这种方法比较容易实现,这种方法不需要自己支持wrap_content和padding。
4.继承特定的ViewGroup(比如LinearLayout)
这种方发也比较常见,当某种效果看起来很想几种View组合在一起的时候,可以采用这种方法实现,这种方法不需要自己处理测量和布局两个过程,一般来说方法2介意实现的效果方法4也可以实现,区别在于方法2更接近低层。
我们需要去找到一种代价最小,最高效的方法去实现。
三.自定义View须知
- 让View支持wrap_content(对于直接继承自View或者ViewGroup的控件,如果不在onMeasure中对wrap_content作特殊处理,那么wrap_content属性将失效)
- 支持padding和margin
直接继承View的控件,如果不在draw方法中处理padding,padding属性是无法起作用的。直接继承自ViewGroup的控件需要在onMeasure和onLayout方法中考虑padding和子元素的margin对其造成的影响。 - 多线程应直接使用post方式
View的内部本身提供了post系列的方法,完全可以替代Handler的作用,使用起来更加方便、直接。 - 避免内存泄漏
主要针对View中含有线程或动画的情况:当View退出或不可见时,记得及时停止该View包含的线程和动画,否则会造成内存泄露问题。
启动或停止线程/ 动画的方式:
启动线程/动画:使用view.onAttachedToWindow(),因为该方法调用的时机是当包含View的Activity启动的时刻
停止线程/动画:使用view.onDetachedFromWindow(),因为该方法调用的时机是当包含View的Activity退出或当前View被remove的时刻
- 处理好滑动冲突
四.自定义View示例
1.继承View重写onDraw方法
自定义View实现圆的绘制,它会在自己的中心点以宽/高的最小值为直径绘制一个实心圆
public class CircleView extends View {
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CircleView(Context context) {
super(context);
init();
}
public CircleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
int mColor = Color.RED;
mPaint.setColor(mColor);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
int radius = Math.min(width,height) / 2;
canvas.drawCircle(width / 2, height / 2,radius,mPaint ); //画圆
}
}
布局代码:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#ffffff">
<com.example.four_view_workingprincipletest.CircleView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#000000"
android:id="@+id/circleView1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
运行效果图:
- 问题1:此时,当我们把layout_width改为wrap_content,效果和上面完全一样。
这是上面提到的让View支持wrap_content问题,具体的解决在Android View(四)——View的工作原理这里给出 - 问题2:当在布局文件中加上android:padding="20dp"不起作用
针对padding问题,需要我们在绘制时考虑一下padding,修改onDraw方法如下
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();