第四章-View的工作原理(自定义View)

一、自定义View的分类
继承View重写onDraw方法
二、自定义View的须知
在这里插入图片描述
在这里插入图片描述

三、自定义View的实例
直接继承View
这个view处理了一些常见的问题,是一个比较规范的示例。
支持xml配置wrap_parent
支持padding
支持xml自定义属性

View的代码:

package com.example.custom;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import com.example.testgesturedetector.R;

public class CircleView extends View {
    private static final String TAG = "CircleView";
    //颜色
    private int mColor = Color.RED;
    //画笔样式
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    public CircleView(Context context) {
        super(context);
        init();
        Log.d(TAG,"one");
    }

	//实际测试是调用到这个构造
    public CircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        Log.d(TAG,"two");
        TypedArray type = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        //没有指定颜色的话默认红色
        mColor = type.getColor(R.styleable.CircleView_circle_color, Color.RED);
        type.recycle();
        init();
    }

    //这里处理自定义的属性
    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        Log.d(TAG,"mColor = " + mColor);
        TypedArray type = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        //没有指定颜色的话默认红色
        mColor = type.getColor(R.styleable.CircleView_circle_color, Color.RED);
        type.recycle();
        init();

    }

    //初始化
    private void init() {
        //设置颜色
        mPaint.setColor(mColor);
    }

    //解决wrap_parent不生效问题,设置一个默认的宽高200
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        Log.d(TAG,"widthSpecSize = " + widthSpecSize + " || heightSpecSize = " + heightSpecSize);

        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(200, 200);
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(200, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, 200);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.d(TAG,"changed=" + changed + " || left=" + left + " || top=" + top + " || right=" + right + " || bottom=" + bottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Log.d(TAG,"onDraw");

        //优化了padding的代码
        super.onDraw(canvas);
        //padding值
        int left = getPaddingLeft();
        int right = getPaddingRight();
        int top = getPaddingTop();
        int bottom = getPaddingBottom();
        //View的宽
        int width = getWidth() - left - right;
        //View的高
        int height = getHeight() - top - bottom;
        //圆的半径 = 宽和高比较出的数 / 2
        int radiu = Math.min(width, height) / 2;
        //绘制圆
        canvas.drawCircle(left + width / 2, top + height / 2, radiu, mPaint);
    }

}

注意:mColor = type.getColor(R.styleable.CircleView_circle_color, Color.RED); 这个里面的CircleView_circle_color(是declare-styleable 和 attr)

测试的activity的xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.example.custom.CircleView
        android:id="@+id/cv"
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:background="#000000"
        android:layout_margin="20dp"
        android:padding="20dp"//支持padding
        app:circle_color="#00ff00"//自定义属性
        />

</LinearLayout>

自定义属性,在res/values 里面创建atts_custom_view.xml属性配置文件(文件名称可以自己配置)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleView">
        <attr name="circle_color" format="color" />
    </declare-styleable>
</resources>

实际真机运行OK,效果如下:
在这里插入图片描述
logcat打印如下:
在这里插入图片描述

继承ViewGroup派生出来的Layout

在这里插入图片描述
在这里插入图片描述

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 widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
	int widthSpecSize = MeasureSpec.getMode(widthMeasureSpec);
	int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
	int heightSpecSize = MeasureSpec.getMode(heightMeasureSpec);
	if(childCount ==0){
		setMeasuredDimension(0, 0);
	}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);
	}
}

在这里插入图片描述

protected void onLayout(boolean changed, int l, int t, int r, int b) {
	int childLeft = 0;
	final int childCount = getChildCount();
	mChildSize = childCount;
	for (int i = 0; i < childCount; i++) {
		final View childView = getChildAt(i);
		final int childWidth = childView.getMeasuredWidth();
		mChildWidth = childWidth;
		childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight());
		childLeft += childWidth;
	}
}

在这里插入图片描述

public class HorizontalScrollViewEx extends ViewGroup {

    private int mChildrenSize;
    private int mChildWidth;
    private int mChildIndex;

    //分别记录上次滑动的坐标
    private int mLastX = 0;
    private int mLastY = 0;

    //分别记录上次滑动的坐标
    private int mLastXIntercept = 0;
    private int mLastYIntercept = 0;

    private Scroller mScroller;

    private VelocityTracker mVelocityTracker;

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

    public HorizontalScrollViewEx(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public HorizontalScrollViewEx(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        if (mScroller == null) {
            mScroller = new Scroller(getContext());
            mVelocityTracker = VelocityTracker.obtain();
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        int x = (int) ev.getX();
        int y = (int) ev.getY();

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                    intercepted = true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastXIntercept;
                int deltaY = y - mLastYIntercept;
                if (Math.abs(deltaX) > Math.abs(deltaY)) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
        }
        mLastX = x;
        mLastY = y;
        mLastXIntercept = x;
        mLastYIntercept = y;
        return intercepted;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mVelocityTracker.addMovement(event);
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int scrollX = getScrollX();
                mVelocityTracker.computeCurrentVelocity(1000);
                float xVelocity = mVelocityTracker.getXVelocity();

                if (Math.abs(xVelocity) >= 50) {
                    mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
                } else {
                    mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
                }
                mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
                int dx = mChildIndex * mChildWidth - scrollX;
                smoothScrollBy(dx, 0);
                mVelocityTracker.clear();
                break;
        }
        mLastX = x;
        mLastY = y;
        return true;
    }

    @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 widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getMode(heightMeasureSpec);
        if (childCount == 0) {
            setMeasuredDimension(0, 0);
        } 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);
        }
    }

    @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);
            final int childWidth = childView.getMeasuredWidth();
            mChildWidth = childWidth;
            childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight());
            childLeft += childWidth;
        }
    }

    private void smoothScrollBy(int dx, int dy) {
        mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        mVelocityTracker.recycle();
        super.onDetachedFromWindow();
    }
}

下面给写常用的开源的自定义view网址:
https://github.com/Trinea/android-open-project/
https://github.com/lightSky/Awesome-MaterialDesign

到此为止,想必对view有了比较全面的认识。

发布了126 篇原创文章 · 获赞 42 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/gaopinqiang/article/details/105207865