View的工作原理(自定义View)

  为了更好的自定义View,还需要掌握View的底层工作原理,比如View的测量流程,布局流程以及绘制流程。为了更好的理解View的测量过程,我们还需要理解MeasureSpec

 

一 MeasureSpec

(1).MeasureSpec

MeasureSpec代表了一个32int值,高2位代表SpecMode,低30位代表SpecSizeSpecMode 是指测量模式,而SpecSize是指在某种测量模式下的规格大小。MeasureSpec通过将SpecModeSpecSize打包成一个int值来避免过多的对象内存分配。为了方便操作,其提供了打包和解压方法。SpecModeSpecSize也是一个int值,一组SpecModeSpecSize可以打包为一个MeasureSpec,而一个MeasureSpec可以通过解包的形式来得出其原始的SpecMode SpecSize ,需要注意的是这里提到的MeasureSpec是指MeasureSpec所代表的int值,而不是它本身。

SpecMode有三类,每一类,都表示不同的含义。

 

UNSPECIFIED

父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量状态。

 

EXACTLY

 

父容器已经检测出View所需要的精确大小,这个时候,View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值两种模式。

 

AT_MOST

父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同的View的具体实现了。它对应于LayoutParamswrap_content.

 

(2).MeasureSpecLayoutParams的关系

系统内部是通过MeasureSpec来进行View的测量,但是正常情况下我们使用View指定MeasureSpec,尽管如此,我们还可以给View设置LayoutParams。在View测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasrueSpec确定View测量后的宽和高。需要注意的是,MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定ViewMeasureSpec,从而进一步决定View的尺寸。

 

只要提供父容器的MeasureSpec和子元素的LayoutParams,就可以快速地确定出了子元素的MeasureSpec了,有了MeasureSpec就可以进一步确定出了子元素的测量后的大小了。

 

二 View的工作原理

View的工作流程主要是指measurelayout,draw这三大流程,即测量,布局和绘制,其中measure确定了View测量的宽和高,layout确定了View的位置,而draw则将View绘制到屏幕上。

(1) .measure过程

(2) Layout过程

(3) Draw过程

//ps这一块太多,过一段时间再写,反正还没人看。。。。。。。。。

 

三 自定义View

自定义View的分类标准不唯一,而大体上有四类。

 

(1).继承View重写onDraw方法

这种方法主要用于实现一些不规则的效果,即折后在那个效果不方便通过布局的组合方式来达到,往往需要静态或者动态的显示一些不规则的图形,这需要通过绘制的方法来实现,即重写onDraw方法。采用这种方法需要自己支持wrap_content,并且padding也需要自己处理。

(2). 继承ViewGroup派生特殊的Layout

这种方法主要用于实现自定义布局,即除了LinearLayoutRelativeLayoutFrameLayout这几种系统布局之外,我们重新定义一种新的布局,需要核实的处理ViewGroup的测量,布局这两个过程,并同时处理子元素的测量和布局过程。

(3). 继承特定的View(比如TextView

这种方法比较常见,一般是用于扩展那某种已有的View的功能,比如TextVeiw,这种方法比较容易实现,不需要自己支持wrap_contentpadding

(4). 继承特定的ViewGroup(比如LinearLayout

这种方法比较常见,效果看起来就像集中View组合在一起的时候,可以采用这种方法实现,不需要自己处理ViewGroup的测量和布局过程。

 

自定义View有一下问题需要注意

1.让View支持wrap_content

这是因为直接继承了View或者ViewGroup的控件,如果不在onMeasure中对wrap_content做特殊处理,那么当外界布局中使用了wrap_content时就无法打到预期效果。

2.如果有必要。让View支持padding

这是因为直接继承View的控件,如果不在draw方法中处理padding,那么padding属性是无法起作用的。另外,直接继承自ViewGroup的控件需要在onMeasureonLayout中考虑padding和子元素的margin对其造成的影响,不然将导致padding和子元素margin失效。

3.尽量不要在View中使用Handler,没必要。

这是因为View内部本身提供了post系列的方法,完全可以代替Handler的作用

4.View中如果有线程或者动画,需要及时停止。

如果有线程或者动画需要停止时,那么onDetachedFromWindow是一个很好的时机。当包含ViewActivity退出或者当前ViewRemove时,ViewonDetachedFromWindow方法会被调用,和此方法对应的是onAttachedToWindow.同时,当View变得不可见时,我们也需要停止线程和动画,如果不及时处理这种问题,有可能造成内存泄露。

5.View带滑动嵌套情形时,需要处理好滑动冲突。


最后 附一自定义View例子:

public class HorizontalScrollViewEx2 extends ViewGroup {
    private static final String TAG = "HorizontalScrollViewEx2";

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

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

    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;

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

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

    public HorizontalScrollViewEx2(Context context, AttributeSet attrs,
            int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            mLastX = x;
            mLastY = y;
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
                return true;
            }
            return false;
        } else {
            return true;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent action:" + event.getAction());
        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 deltaX = x - mLastX;
            int deltaY = y - mLastY;
            Log.d(TAG, "move, deltaX:" + deltaX + " deltaY:" + deltaY);
            scrollBy(-deltaX, 0);
            break;
        }
        case MotionEvent.ACTION_UP: {
            int scrollX = getScrollX();
            int scrollToChildIndex = scrollX / mChildWidth;
            Log.d(TAG, "current index:" + scrollToChildIndex);
            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();
            Log.d(TAG, "index:" + scrollToChildIndex + " dx:" + dx);
            break;
        }
        default:
            break;
        }

        mLastX = x;
        mLastY = y;
        return true;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int measuredWidth = 0;
        int measuredHeight = 0;
        final int childCount = getChildCount();
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        if (childCount == 0) {
            setMeasuredDimension(0, 0);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            final View childView = getChildAt(0);
            measuredHeight = childView.getMeasuredHeight();
            setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight());
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            final View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth() * childCount;
            setMeasuredDimension(measuredWidth, heightSpaceSize);
        } else {
            final View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth() * childCount;
            measuredHeight = childView.getMeasuredHeight();
            setMeasuredDimension(measuredWidth, measuredHeight);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        Log.d(TAG, "width:" + getWidth());
        int childLeft = 0;
        final int childCount = getChildCount();
        mChildrenSize = childCount;

        for (int i = 0; i < childCount; i++) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != View.GONE) {
                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部分,需要了解的,打听就是这些方面,有些不完善的地方,有空我再补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值