重学Android - 自定义View
View和ViewGroup
View是Android所有控件的基类,同时ViewGroup也是继承自View。ViewGroup作为View或者ViewGroup这些组件的容器,派生了 多种布局控件子类,比如LinearLayout、RelativeLayout等
Android坐标系
Android视图坐标系
View获取自身宽高
- getHeight():获取View自身高度
- getWidth():获取View自身宽度
MotionEvent提供的方法
我们看上图那个深蓝色的点,假设就是我们触摸的点,我们知道无论是View还是ViewGroup,最终的点击事件都会由onTouchEvent(MotionEvent event)方法来处理,MotionEvent也提供了各种获取焦点坐标的方法:
- getX():获取点击事件距离控件左边的距离,即视图坐标
- getY():获取点击事件距离控件顶边的距离,即视图坐标
- getRawX():获取点击事件距离整个屏幕左边距离,即绝对坐标
- getRawY():获取点击事件距离整个屏幕顶边的的距离,即绝对坐标
View滑动的六种方法
layout(int l, int t, int r, int b)
通过修改View的left、top、right、bottom这四种属性来控制View的坐标。
传进来里面的四个参数分别是View的四个点的坐标,它的坐标不是相对屏幕的原点,而且相对于它的父布局来说的。
public boolean onTouchEvent(MotionEvent event) {
//获取到手指处的横坐标和纵坐标
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
//计算移动的距离
int offsetX = x - lastX;
int offsetY = y - lastY;
//调用layout方法来重新放置它的位置
layout(getLeft()+offsetX, getTop()+offsetY,
getRight()+offsetX , getBottom()+offsetY);
break;
}
return true;
}
offsetLeftAndRight()与offsetTopAndBottom()
将ACTION_MOVE中的代码替换成如下代码:
case MotionEvent.ACTION_MOVE:
//计算移动的距离
int offsetX = x - lastX;
int offsetY = y - lastY;
//对left和right进行偏移
offsetLeftAndRight(offsetX);
//对top和bottom进行偏移
offsetTopAndBottom(offsetY);
break;
LayoutParams(改变布局参数)
LayoutParams主要保存了一个View的布局参数,因此我们可以通过LayoutParams来改变View的布局的参数从而达到了改变View的位置的效果。同样的我们将ACTION_MOVE中的代码替换成如下代码:
LinearLayout.LayoutParams layoutParams= (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
因为父控件是LinearLayout,所以我们用了LinearLayout.LayoutParams,如果父控件是RelativeLayout则要使用RelativeLayout.LayoutParams。除了使用布局的LayoutParams外,我们还可以用ViewGroup.MarginLayoutParams来实现:
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
scollTo与scollBy
scollTo(x,y)表示移动到一个具体的坐标点
scollBy(dx,dy)则表示移动的增量为dx、dy。
其中scollBy最终也是要调用scollTo的。scollTo、scollBy移动的是View的内容。
如果在ViewGroup中使用则是移动他所有的子View/子控件
Scroller
我们用scollTo/scollBy方法来进行滑动时,这个过程是瞬间完成的,所以用户体验不大好。这里我们可以使用Scroller来实现有过度效果的滑动。
Activity的构成
一个Activity包含一个window对象,这个对象是由PhoneWindow来实现的,PhoneWindow将DecorView做为整个应用窗口的根View,而这个DecorView又将屏幕划分为两个区域一个是TitleView一个是ContentView,而我们平常做应用所写的布局正是展示在ContentView中的。
MeasureSpec
MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配。
MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一 起才能决定View的MeasureSpec,从而进一步决定View的宽/高。对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的 LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高。
MeasureSpec和LayoutParams的对应关系
前面已经提到,对于普通View,其MeasureSpec由父容器的 MeasureSpec和自身的LayoutParams来共同决定,那么针对不同的父容器和View本身不同的LayoutParams, View就可以有多种MeasureSpec。这里简单说一下,当View采用固定宽/高的时候,不管父容器的 MeasureSpec是什么,View的MeasureSpec都是精确模式并且其大小遵循Layoutparams中的大小。当View的 宽/高是match_parent时,如果父容器的模式是精准模式,那么View也是精准模式并且其大小是父容器的剩 余空间;如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。当 View的宽/高是wrap_content时,不管父容器的模式是精准还是最大化,View的模式总是最大化并且大小不 能超过父容器的剩余空间。可能读者会发现,在我们的分析中漏掉了UNSPECIFIED模式,那是因为这个 模式主要用于系统内部多次Measure的情形,一般来说,我们不需要关注此模式。
通过上图可以看出,只要提供父容器的MeasureSpec和子元素的LayoutParams,就可以快速地确定出 子元素的MeasureSpec了,有了MeasureSpec就可以进一步确定出子元素测量后的大小了。
自定义View的分类
-
继承View重写onDraw方法
这种方法主要用于实现一些不规则的效果,即这种效果不方便通过布局的组合方式来达到,往往需要 静态或者动态地显示一些不规则的图形。很显然这需要通过绘制的方式来实现,即重写onDraw方法。 采用这种方式需要自己支持wrap_content,并且padding也需要自己处理。 -
继承ViewGroup派生特殊的Layout
这种方法主要用于实现自定义的布局,即除了LinearLayout、RelativeLayout、FrameLayout这几种系统 的布局之外,我们重新定义一种新布局,当某种效果看起来很像几种View组合在一起的时候,可以采用这 种方法来实现。采用这种方式稍微复杂一些,需要合适地处理ViewGroup的测量、布局这两个过程,并同时处理子元素的测量和布局过程。 -
继承特定的View(比如TextView)
这种方法比较常见,一般是用于扩展某种已有的View的功能,比如TextView,这种方法比较容易实 现。这种方法不需要自己支持wrap_content和padding等。 -
继承特定的ViewGroup(比如LinearLayout)
这种方法也比较常见,当某种效果看起来很像几种View组合在一起的时候,可以采用这种方法来实 现。采用这种方法不需要自己处理ViewGroup的测量和布局这两个过程。需要注意这种方法和方法2的区 别,一般来说方法2能实现的效果方法4也都能实现,两者的主要差别在于方法2更接近View的底层。
自定义View须知
-
让View支持wrap_content
这是因为直接继承View或者ViewGroup的控件,如果不在onMeasure中对wrap_content做特殊处理,那么当外界在布局中使用wrap_content时就无法达到预期的效果。
解决方案:在onMeasure中添加对wrap_content情况的判断,为wrap_content可以设置一个默认大小。
@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);
//wrap_content时设置默认大小为200
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);
}
}
-
如果有必要,让你的View支持padding
这是因为直接继承View的控件,如果不在draw方法中处理padding,那么padding属性是无法起作用 的。另外,直接继承自ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其 造成的影响,不然将导致padding和子元素的margin失效。
解决方案:针对padding的问题,也很简单,只要在绘制的时候考虑一下padding即可,因此我们需要对 onDraw稍微做一下修改,修改后的代码如下所示。
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingLeft();
final int paddingTop = getPaddingLeft();
final int paddingBottom = getPaddingLeft();
int width = getWidth() -paddingLeft -paddingRight;
int height = getHeight() -paddingTop -paddingBottom;
int radius = Math.min(width,height) / 2;
canvas.drawCircle(paddingLeft + width / 2,paddingTop + height/2,radius, mPaint);
}
-
尽量不要在View中使用Handler,没必要
这是因为View内部本身就提供了post系列的方法,完全可以替代Handler的作用,当然除非你很明确地
要使用Handler来发送消息。 -
View中如果有线程或者动画,需要及时停止,参考View#onDetachedFromWindow
这一条也很好理解,如果有线程或者动画需要停止时,那么onDetachedFromWindow是一个很好的时 机。当包含此View的Activity退出或者当前View被remove时,View的onDetachedFromWindow方法会被调 用 , 和 此 方 法 对 应 的 是 onAttachedToWindow , 当 包 含 此 View 的 Activity 启 动 时 , View 的 onAttachedToWindow方法会被调用。同时,当View变得不可见时我们也需要停止线程和动画,如果不及 时处理这种问题,有可能会造成内存泄漏。 -
View带有滑动嵌套情形时,需要处理好滑动冲突
本文地址:https://blog.csdn.net/qq_40595341/article/details/109643302
上一篇: 杯酒释兵权中,赵匡胤为什么留下了张永德?
下一篇: 详解Java实现设计模式之责任链模式