自定义View

分类:
  • 继承View重写onDraw方法:这种情况下,需要自己支持wrap_content,并且padding也需要自己处理。
  • 继承ViewGroup派生特殊的layout:需要处理ViewGroup的测量和布局这两个过程,并同时处理子元素的测量和布局过程。
  • 继承特定的View(比如TextView):这种方法一般不需要自己处理wrap_content和padding。
  • 继承特定的ViewGroup(比如LinearLayout)
自定义View须知:
  • 对于继承View和ViewGroup的情况下,需要处理wrap_content和padding
  • 尽量不要在View中使用handler,因为View内部本身就提供了post系列。
  • View中如果有线程或动画,需要及时停止。当View不可见或View的Activity推出时,要及时停止线程和动画,否则有可能造成内存泄漏。我们一般是在onDetacheddFromWindow中去停止,因为当View不可见或Activity退出时,该方法会被调用。
  • View带有滑动嵌套情形时,需要处理好滑动冲突。
示例一:继承View重写onDraw方法

这里我们来实现自定义圆,首先看到onMeasure方法:
解析传入的宽高 --- 考虑wrap_contet情况 --- 若存在,则设置一个默认的值

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取到width,height对应的mode和size
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        //考虑wrap_content的情况,需要为其设置默认的值,这里设置为200dp
        //对于宽高都设定为wrap_content时
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(200, 200);
        }
        //对于高设定为wrap_content的情况
        else if (widthSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(200, heightSpecSize);
        }
        //对于宽设定为wrap_content的情况
        else if (heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize, 200);
        }
    }

接下来是,实现onDraw方法:
要注意的一点是,这里要处理padding的情况

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //获取到padding值
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        //获取到真实的宽高
        int height = getHeight() - paddingTop - paddingBottom;
        int width = getWidth() - paddingLeft - paddingRight;
        //以最小的值/2作为半径
        int radius = Math.min(width, height) / 2;
        //进行画圆
        canvas.drawCircle(paddingLeft + width/2 , paddingTop + height/2, radius, mPaint);
    }

我们还可以为其设置自定义属性:
a. 在res/values中创建一个xml文件:



    
        
    

其中,name表示为属性名,format表示为属性类型,这里是color类型。

b. 在布局中引入:xmlns:app = "http://schemas.android.com/apk/res-auto"
并设置自定义控件:


我们可以看到其中的app:cirle_color表示为自定义的属性。

c. 在自定义View中获取到属性:

//获取到自定义属性
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
//获取到自定义属性的值,其中第二个参数为默认参数,表示若不存在,则设置为默认值
mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);

完整代码如下:

public class CircleView extends View {
    //定义颜色和画笔
    private int mColor = Color.RED;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    public CircleView(Context context) {
        super(context);
        init();
    }

    public CircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //获取到自定义属性
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        //获取到自定义属性的值,其中第二个参数为默认参数,表示若不存在,则设置为默认值
        mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
        init();
    }

    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
        init();
    }

    //给画笔设置颜色
    private void init(){
        mPaint.setColor(mColor);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取到width,height对应的mode和size
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        //考虑wrap_content的情况,需要为其设置默认的值,这里设置为200dp
        //对于宽高都设定为wrap_content时
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(200, 200);
        }
        //对于高设定为wrap_content的情况
        else if (widthSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(200, heightSpecSize);
        }
        //对于宽设定为wrap_content的情况
        else if (heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize, 200);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //获取到padding值
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        //获取到真实的宽高
        int height = getHeight() - paddingTop - paddingBottom;
        int width = getWidth() - paddingLeft - paddingRight;
        //以最小的值/2作为半径
        int radius = Math.min(width, height) / 2;
        //进行画圆
        canvas.drawCircle(paddingLeft + width/2 , paddingTop + height/2, radius, mPaint);
    }
}

实现效果:


自定义View_第1张图片
自定义View.png
实例二:自定义的ViewGroup

实现一个类似ViewPager的自定义ViewGroup:

a. 实现onMeasure方法:
遍历孩子元素进行测量 --- 考虑wrap_content情况:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //定义两个变量,作为测量的宽和高
        int measureWidth = 0;
        int measureHeight = 0;
        //获取到孩子个数
        final int childCount = getChildCount();
        //遍历孩子元素,对孩子元素分别进行测量
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        
        //解析宽高,获取到模式和大小
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        //考虑wrap_content情况
        
        //孩子为0的情况下
        if (childCount == 0){
            setMeasuredDimension(0 ,0);
        }
        //宽高都是为wrap_content
        else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            final View childView = getChildAt(0);
            //获取到对应的宽度
            measureWidth = childView.getMeasuredWidth() * childCount;
            //设置孩子的高度作为当前高度
            measureHeight = childView.getMeasuredHeight();
            setMeasuredDimension(measureWidth, measureHeight);
        } else if(widthSpecMode == MeasureSpec.AT_MOST){
            final  View childView = getChildAt(0);
            measureWidth = childView.getMeasuredWidth() * childCount;
            setMeasuredDimension(measureWidth, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST){
            final View childView = getChildAt(0);
            measureHeight = childView.getMeasuredHeight();
            setMeasuredDimension(widthSpecSize, measureHeight);
        }
    }

b. 实现布局方式:
遍历所有的孩子元素,若孩子元素不为GONE模式,则进行放置其位置。

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childLeft = 0;
        final int childCount = getChildCount();
        mChildrenSize = childCount;
        //遍历所有的孩子元素
        for (int i = 0; i < childCount; i ++){
            final View childView = getChildAt(i);
            //若当前的孩子元素不为GONE,则对该孩子进行放置其位置
            if (childView.getVisibility() != View.GONE){
                final int childWidth = childView.getMeasuredWidth();
                mChildWidth = childWidth;
                childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight());
                childLeft += childWidth;
            }
        }
    }

你可能感兴趣的:(自定义View)