Android群英传学习——控件架构与自定义控件

控件架构

一般控件分为两类,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:内层已处理,不再传给外层。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值