一、为什么要自定义控件
1.特定的显示风格 :比如app需要一些特殊的效果,是原生组件所没有的。
2.处理特有的用户交互:一些特殊的与用户交互的方式。比如说,原本TextView不能滑动里面的文字,我们需要它的文字可以被滑动。
3.优化我们的布局:我们可以通过各种嵌套来实现我们的布局,但是绘制和测量会比较慢,可以通过自定义控件,通过某种方式来提高我们的效率(指的是绘制、测量等)。
4.封装等…… :一些用的次数比较多的空间组合,比如顶部标题栏等,可以自定义一个控件封装它。
二、如何自定义控件
1.自定义属性的声明与获取
2.测量onMeasure
3.布局onLayout(ViewGroup)
4.绘制onDraw
5.onTouchEvent
6.onInterceptTouchEvent(ViewGroup)
1.自定义属性声明与获取
1)分析需要的自定义属性
2)在res/values/attrs.xml定义声明
3)在Layout xml文件中进行使用
4)在View的构造方法中进行获取
xml文件中:
<?xml version="1.0" encoding="utf-8"?>
<resource>
<attr name="icon" format="reference"></attr>
<attr name="color" format="color"></attr>
<attr name="text" format="string"></attr>
<attr name="text_size" format="dimension"></attr>
<declare-styleable name="ChangeColorIconWithText">
<attr name="icon"></attr>
<attr name="color"></attr>
<attr name="text"></attr>
<attr name="text_size"></attr>
</declare-styleable>
</resource>
java代码中:
TypeArray a = context.obtainStyledAttributes(attrs, R.styleable.ChangeColorIconWithText);
int n = a.getIndexCount();
for(int i = 0; i < n; i++){
int attr = a.getIndex(i);
switch(attr){
case R.styleable.ChangeColorIconWithText_icon:
BitmapDrawable drawable = (BitmapDrawable)a.getDrawable(attr);
mIconBitmap = drawable.getBitmap();
break;
case R.styleable.ChangeColorIconWithText_color:
mColor = a.getColor(attr, 0xFF45C01A);
break;
case R.styleable.ChangeColorIconWithText_text:
mText = a.getString(attr);
break;
case R.styleable.ChangeColorIconWithText_text_size:
mTextSize = (int) a.getDimension(attr, TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics()));
break;
}
}
a.recycle();
2.测量onMeasure
1) EXACTLY, AT_MOST, UNSPECIFIED
2) MeasureSpec
3) SetMeasuredDimension
4) requestLayout()
view需要测量自身到底多大、什么样子。
测量由两部分组成,一个是测量的模式MODE和测量的具体值。
EXACTLY 设置了一个明确的值,例如100dp,match_parent等。
AT_MOST 最多不超过一个值,例如 wrap_content,宽度高度由自身决定,并且最大不能超过父控件。
UNSPECIFIED 大小没有限制,例如在srcollview、 listview等中的子控件高度是不可能限制。
MeasuerSpec是一个辅助类,测量的值一般会封装为MeasureSpec,下面是示例代码:
private int measureHeight(int heightMeasureSpec){
int result = 0;
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
//如果模式为EXACTLY,直接使用size
if(mode == MeasureSpec.EXACTLY){
result = size;
}else{
//计算自身需要的高度
result = getNeedHeight() + getPaddingTop() + getPaddingBottom();
//如果模式为AT_MOST,需要与size取大的值
if(mode == MeasureSpec.AT_MOST){
result = Math.min(result, size);
}
}
return result;
}
测量后将result通过setMeasuredDimension方法设置。
requestLayout方法的执行会重新测量自身大小。但不会重新绘制。
3.布局onLayout(ViewGroup)
1)决定子View的位置
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; i < childCount; i++){
final View child = getChildAt(i);
if(child.getVisiblity() == GONE){
continue;
}
left = caculateChildLeft();//计算childView layout的左上角x坐标
top = caculateChildTop();//计算childView layout的左上角y坐标
child.layout(left, top, left + cWidth, top + cWidth);
}
}
4.绘制onDraw
1)绘制内容区域
2)invalidate(), postInvalidate();
3)Canvas.drawXXX
4)translate、rotate、scale、skew
5)save()、restore()
绘制内容的显示,通过调用Canvas相关API。其间可以巧妙的利用translate、rotate,之后别忘了调用save()、restore()对canvas进行保存,对于这两个方法对理解呢,可以去看moble_xie大神博客(:P)
http://blog.csdn.net/lovexieyuan520/article/details/50683249
@Override
protected synchronized void onDraw(Canvas canvas){
//使用Canvas相关API绘制 anything you want
}
重新绘制的两个方法, 主线程中invalidate(),子线程中postInvalidate()。
5.onTouchEvent
1)ACTION_DOWN、ACTION_MOVE、ACTION_UP
2)ACTION_POINTER_DOWN、ACTION_POINTER_UP
3)parent.requestDisallowInterceptTouchEvent(true); 告诉父控件不要拦截touch事件,而由子控件处理事件。
4)VelocityTracker :hudashi大神博客链接, http://blog.csdn.net/hudashi/article/details/7352157
@Override
public boolean onTouchEvent(MotionEvent ev){
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
switch(action){
case MotionEvent.ACTION_DOWN:
//进行一些初始化赋值等的操作
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
//如果需要进行速度判断
int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
//释放各种资源、重置变量
break;
case MotionEvent.ACTION_CANCEL:
//释放各种资源、重置变量
break;
case MotionEvent.ACTION_POINTER_DOWN:
//如果支持多指触控,在此设置activePointer
final int index = ev.getActionIndex();
mLastMotionY = (int) ev.getY(index);
mActivePointerId = ev.getPointerId(index);
break;
case MotionEvent.ACTION_POINTER_UP:
//如果支持多指触控,且抬起的是activitePointer,则重置选择一个手指为活跃的手指
if(pointerId == mActiviPointerId){
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastMotionY = (int) ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
if(mVelocityTracker != null){
mVeloacityTracker.clear();
}
}
break;
}
return true;
}
6.onInterceptTouchEvent(ViewGroup)
1)ACTION_DOWN、ACTION_MOVE、ACTION_UP
2)ACTIOON_POINTER_DOWN、ACTION_POINTER_UP
3)决定是否拦截该手势
@Override
public boolean onInterceptTouchEvent(MotionEvent ev){
int action = ev.getAction();
swicth(action & MotionEvent.ACTION_MASK){
case MotionEvent.ACTION_MOVE:
final int activePointerId = mActivePointerId;
if(activePointerId == INVALID_POINTER){
break;
}
final int pointerIndex = ev.findPointerIndex(activePointerId);
final int y = (int) ev.getY(pointerIndex);
final iny yDiff = Math.abs(y - mLastMotionY);
if(yDiff > mTouchSlap){
mIsBeingDragged = true;
mLastMotionY = y;
}
break;
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_UP:
break;
}
return mIsBeingDragged;
}
这个方法返回true,代表viewgroup拦截了事件,不交给子控件处理。
三、其它
1.onSaveInstanceState、onRestoreInstanceState
这两个方法是Activity也拥有的,当acitivity容易被销毁时第一个方法会被调用,可以进行一些临时数据的保存,例如以下的几种情况:
1)当用户按下Home键的时候
2)长按Home键,选择运行其它程序时
3)按下电源键,关闭屏幕显示时
4)从A activity 启动新的activity时
5)屏幕方向切换时
6)…
而第二个方法,onRestoreInstanceState,则是在执行第一个方法onSaveInstanceState后,确实被系统销毁了,并且再回到原来的activity时,会执行的方法,可以将之前保存的数据通过参数获取出来。
view的两个方法,是在activity执行这两个方法时执行的,也是用于存储临时数据,比如view的一些状态等。
@Override
protected Parcelable onSaveInstanceState(){
Bundle bundle = new Bundle();
//将要保存等数据存在bundle中
//...
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state){
if(state instanceof Bundle){
Bundle bundle = (Bundle) state;
//获取并使用Bundle对象里面的数据
//...
super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATUS));
return;
}
super.onRestoreInstanceState(state);
}
2.ViewConfiguration(mTouchSlop等)
3.ScaleGestrueDetector
4.ViewDragHelper 链接到鸿洋大神博客 http://blog.csdn.net/lmj623565791/article/details/46858663
5.以及更多…
/………..后记: 初步了解就到这里(代码源自幕客网视频),文中代码全手打,如果直接粘贴可能有错误的地方 : P ………………/