自定义View——浅解

为什么自定义控件

1.有些特殊的效果是Android原生没有提供的,需要我们根据自身需求自定义从而达到目的效果;
2.有可能是我们的控件与用户的交互方式有些特殊;
3.有时我们的布局需要以各种控件相互嵌套的方式实现,从而使绘制过程很慢,使用自定义控件有时可提升效率;
4.有时一些控件组复用性很高,通过封装成自定义控件可简化代码

如何自定义控件

一、测量onMeasure

1.测量由两个数字决定:一个是测量的模式,一个是值。

2.测量模式包括三个值:
EXACTLY:表示设置明确的值(如普通view被设置固定高度100dp等)
AT_MOST:表示最多不能超过某个值(一般出现在设置wrap_content属性值的时候,其值由自身内容所决定,但最大不会超过父控件)
UNSPECIFIED:没有限制值(一般出现在scrollview或listview中,其子view没有限制)

3.MeasureSpec:作为测量辅助类,其封装了测量模式和值的方法getMode()和getSize(),所操作的对象是onMeasure方法中由父控件传递下来的值。

4.setMeasuredDimension:在测量完成之后,测量所得的值需被传入到这个方法中。

5.requestLayout():用于触发测量,调用该方法就会触发重新测量以及布局。但其并不包含绘制,绘制由invalidate()触发。

示例代码:

private void measureHeight(int heightMeasureSpec)
{
    int result = 0;
    int mode = MeasureSpec.getMode(heightMeasureSpec);
    int size = MeasureSpec.getSize(heightMeasureSpec);
    if(mode == MeasureSpec.EXACTLE){
        result = size;
    }
    else{
        result = getNeedHeight()+getPaddingTop()+getPaddingBotton();
        if(mode == MeasureSpec.AT_MOST){
            result = Math.min(result, size);
        }
    }
    return result;
}

二、布局onLayout(ViewGroup)

1.onLayout作用是父控件决定子view的显示位置,若只是view而不是viewgroup则不会有onLayout过程。

2.尽量将onMeasure中的操作移到该方法当中。因为在整个绘制过程当中onMeasure可能会被调用很多次才能决定最后的值,而onLayout在整个过程中只会触发一次。

3.requestLayout():触发onLayout

示例代码:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b){
    final int childCount = getChildCount();
    for(int i=0; ifinal View child = getChildAr(i);
        if(child.getVisibility() == Gone){
            countinue;
        }
        left = caculateChildLeft();//计算childview layout的左上角的x坐标
        top = caculateChildTop();//计算childview layout的左上角的y坐标
        child.layout(left, top, left+child.width, top+child.height);
    }
}

三、绘制onDraw()

1.绘制的是内容区域,其他区域不用管
2.invalidate()、postInvalidate():触发重绘,后者是在子线程中调用,前者是在UI线程中调用。

四、onTouchEvent

示例代码:

@Override
public boolean onTouchEvent(MotionEvent ev){
    //要考虑加速度的检测,就要初始化该方法
    initVelocityTrackerIfNotExists();
    mVelocityTracker.addMovement(ev);

    final int action = ev.getAction();
    switch(action & MotionEvent.ACTION_MASK){
        case MotionEvent.ACTION_DOWN:
            //进行一些初始化、赋值等操作
            break;
        case MotionEvent.ACTION_MOVE:
            //根据手指的移动做一些交互操作
            break;
        case MontionEvent.ACTION_UP:
            //若需要则进行速度的判断
            int initialVelocity = (int)velocityTracker.getYVelocity(mActivePointerId)
            //释放各种资源、重置变量
            break;
        case MontionEvent.ACTION_CANCEL:
            //释放各种资源、重置变量
            break;
        case MontionEvent.ACTION_PONTER_DOWN:
            //如果考虑多点触控情况,在此要确定谁是activePoniter,即操作控件的手指。因为多点触控其实只有一个点在控制控件。 
            final int index = ev.getActionIndex();
            mLastMontionY = (int)ev.getY(index);
            mActivePointerId = ev.getPointerId(index);//最后一个down下的点会成为pointerid
            break;
        case MotionEvent.ACTION_POINTER_UP:
            //如果支持的是多指且抬起的是activePointer,则重新选择一个手指为activePoint
            if(pointerId == mActivePointerId){
                final int newPointerIndex = pointerIndex == 0 ? 1 :0;
                mLastMotionY = (int)ev.getY(newPointerIndex);
                mActivePointerId = ev.getPointerId(newPointerIndex);
                if(mVelocityTracker != null){
                    mVelocityTracker.clear();
                }
            }
            break;
    }
    return true;
}

五、onInterceptTouchEvent(ViewGroup)
点击事件在转发过程中,父控件是有权利拦截事件的,如果该方法返回true,就说明时间被拦截了。例如scrollview控件就是利用该方法,在其包含能消耗事件的按钮等子控件的情况下依然可以滚动。

你可能感兴趣的:(android)