控件架构
一般控件分为两类,View和ViewGroup。ViewGroup可以包含多个View。通过ViewGroup整个界面上的形成了一个控件树,每个控件树的
根部,都有一个ViewParent对象,作为整棵树的控制对象。
每一个Activity都包含一个Window对象,通常是PhoneWindow,它将一个DecorView作为窗口的根View,它将需要显示的所有内容都显示在Window上。里面View的监听事件,都由WindowManagerService来接收。
DecorView分为TitleView和ContentView两部分。
在Activity中onCreate方法调用setContentView方法后,ActivityManagerService会回调onResume方法,系统把DecorView添加到PhoneWindow当中并显示。
View的测量
MeasureSpec 是一个32位的int值,高2位测量的模式,低30位是测量的大小。
- EXACTLY(精确值模式):设定为具体数值,或者match_parent时使用。
- AT_MOST(最大值模式):设定为wrap_content时使用。
- UNSPECIFIED:不指定测量模式,想多大就多大。
在onMeasure方法中,使用
setMeasuredDimension(measuredWidth,measuredHeight)
针对不同的模式重新设置宽高。
View的绘制
onDraw方法进行绘制,参数为一个Canvas
当创建Canvas对象时,需要传进去一个Bitmap对象,Canvas和Bitmap是联系在一起的,Bitamp对象承载了所有的绘图结果。
ViewGroup的测量
当大小设置为wrap_content时,对子View进行遍历,获得所有的子View大小,来决定自己的大小。其他模式下会根据具体值来指定大小。
测量结束之后,使用onLayout方法来确定子控件的位置。
ViewGroup一般不需要绘制,如果制定了则会调用。使用dispatchDraw方法来遍历子View,调用子View的绘制方法来完成绘制工作。
自定义View
对现有View进行拓展:在ondraw方法的super.onDraw前后绘制效果不同。
后绘制的会覆盖之前绘制的颜色。
Gradint渲染器可以给画笔设置上,产生渐变效果,Matrix矩阵可以作用于Gradint,产生动态的渐变效果。
在onDraw方法中使用postInvalidateDelayed(xx),可以让View每XX秒刷新一次。
创建复合控件
可重用的控件,根据需求可改变显示内容,动作等。
定义属性:
- 在values/attrs.xml中,通过declare-styleable标签定义一个控件的属性。
- 通过TypedArray ta = obtainStyledAttributes(attrs,R.styleable.xx);可以这些属性值。(用完要recycle)
- 动态实例化控件,添加到预定义的ViewGroup即可
- 定义点击事件的时候声明一个接口,实现点击事件即可。
自定义滚动ViewGroup
public class MyScrollLayout extends ViewGroup {
private int mScreenHeight;
private int mLastY;
private Scroller mScroller;
private int mStart;
private int mEnd;
public MyScrollLayout(Context context) {
super(context);
initView(context);
}
public MyScrollLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public MyScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
public MyScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initView(context);
}
//初始化方法,获得屏幕高度初始化Scroller
private void initView(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(
Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
mScreenHeight = dm.heightPixels;
mScroller = new Scroller(context);
}
//通知子View进行测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount= getChildCount();
// 设置ViewGroup的高度
MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
mlp.height = mScreenHeight * childCount;
setLayoutParams(mlp);
for (int i = 0; i < count; i++) {
View childView = getChildAt(i);
measureChild(childView,widthMeasureSpec,heightMeasureSpec);
}
}
//放置子View的位置,每个View占一个屏幕
@Override
protected void onLayout(boolean changed,
int l, int t, int r, int b) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
child.layout(l, i * mScreenHeight,
r, (i + 1) * mScreenHeight);
}
}
}
//处理触摸事件,视图滚动
@Override
public boolean onTouchEvent(MotionEvent event) {
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastY = y;
mStart = getScrollY();
break;
case MotionEvent.ACTION_MOVE:
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
int dy = mLastY - y;
if (getScrollY() < 0) {
dy = 0;
}
if (getScrollY() > getHeight() - mScreenHeight) {
dy = 0;
}
scrollBy(0, dy);
mLastY = y;
break;
case MotionEvent.ACTION_UP:
mEnd = getScrollY();
int dScrollY = mEnd - mStart;
if (dScrollY > 0) {
//向下滑
if (dScrollY < mScreenHeight /
//如果滑动范围太小,恢复原图
mScroller.startScroll(
0, getScrollY(),
0, -dScrollY);
} else {
//滑动距离足够,滑到下一张图
mScroller.startScroll(
0, getScrollY(),
0, mScreenHeight - dScrollY);
}
} else {
//向上划
if (-dScrollY < mScreenHeight / 3) {
mScroller.startScroll(
0, getScrollY(),
0, -dScrollY);
} else {
mScroller.startScroll(
0, getScrollY(),
0, -mScreenHeight - dScrollY);
}
}
break;
}
postInvalidate();
return true;
}
//invalidate调用时会调用此方法,判断滚动是否完成
@Override
public void computeScroll() {
super.computeScroll();
//返回true表示没有完成
if (mScroller.computeScrollOffset()) {
scrollTo(0, mScroller.getCurrY());
postInvalidate();
}
}
}
事件拦截机制
InterceptTouchEvent返回true:由外层向内层拦截。
onTouchEvent返回true:内层已处理,不再传给外层。