android自定义粘性控件,综合使用measure,layout,onTouchEvent,onInterceptTouchEvent等方法

粘性控件

  • 产生粘性控件很简单,我们只需要进行相关的measure和layout并进行事件拦截,和事件处理即可
  • 显示measure方法,measure方法比较复杂,一般我们会根据父容器的测量算子和自身的layoutParams共同决定,这样为了方便起见,全都是让父容器的测量算子决定的
  • 测量代码

    `
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measureWidth(widthMeasureSpec),
                measureHeight(heightMeasureSpec));
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
        }
    }
    `
    
  • 测量的时候先确定自身的大小,之后再确定view的大小(当然也可以先确定view的大小,之后再确定自己的大小,适用于包裹内容的)
  • setMeasuredDimension(measureWidth(widthMeasureSpec),
    measureHeight(heightMeasureSpec));
  • 这样代码是确定自身的大小。我们直接使用父容器传递过来的参数进行相关高度和宽度的确定

     `private int measureWidth(int widthMeasureSpec) {
    int mode = MeasureSpec.getMode(widthMeasureSpec);
    int size = MeasureSpec.getSize(widthMeasureSpec);
    int result = 0;
    if (mode == MeasureSpec.EXACTLY) {
        result = size;
    } else {
        result = 400;
        Context ctx = getContext();
        if (ctx instanceof Activity) {
            Activity a = (Activity) ctx;
            result = a.getWindowManager().getDefaultDisplay().getWidth();
        }
        if (mode == MeasureSpec.AT_MOST) {
            result = Math.min(result, size);
            Log.e("", size + "");
        }
    }
    return result;
    }`
    
  • 上面代码是通过测量算子的mode和size,共同确定width
  • 高度也类似

    `   private int measureHeight(int heightMeasureSpec) {
    int mode = MeasureSpec.getMode(heightMeasureSpec);
    int size = MeasureSpec.getSize(heightMeasureSpec);
    int result = 0;
    if (mode == MeasureSpec.EXACTLY) {
        result = size;
    } else {
        result = 400;
        int count = getChildCount();
        if (count > 0) {
            View view = getChildAt(0);
            view.measure(0, MeasureSpec.makeMeasureSpec(
                    Integer.MAX_VALUE >> 2, MeasureSpec.EXACTLY));
            result = view.getMeasuredHeight();
        }
        if (mode == MeasureSpec.AT_MOST) {
            // 当为warp_content的时候 给一个最小的默认值
            result = Math.min(result, size);
        }
    }
    return result;
    }`
    
  • 为什么我们需要确定我们自身的宽度和高度呢?是因为,如果我们不修改测量算子的话,那么测量的模式是MEASURE_ATMOST,size是父容器给我们传递的宽度和高度,因此,我们想手动的修改的话,就需要修改测量算子值(mode和size)
  • 下面代码是测量孩子的

    `@Override
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        // TODO Auto-generated method stub
        // super.measureChild(child, parentWidthMeasureSpec,
        // parentHeightMeasureSpec);
        int height = MeasureSpec.getSize(parentHeightMeasureSpec);
        child.measure(MeasureSpec.makeMeasureSpec(LayoutParams.MATCH_PARENT,
                MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height,
                MeasureSpec.EXACTLY));
    }`
    
  • 孩子的测量算子宽度使用的是matchParent和EXACTLY。高度我们使用的是父容器的高度,也就是我们粘性控件的高度,mode也是EXACTLY
  • 自身的高度和孩子的高度都测量完了,剩下的我们就是layout了

    `protected void onLayout(boolean changed, int l, int t, int r, int b) {
    // zhihou zaizheli xunhuan diaoyong childde layout
    int count = getChildCount();
    
    for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        child.layout(0, i * child.getMeasuredHeight(), getMeasuredWidth(),
                (i + 1) * child.getMeasuredHeight());
    
    }
    totalHeight = (getChildCount() - 1) * getMeasuredHeight();
    }`
    
  • 代码很简单,就是遍历孩子,调用layout方法
  • 接下来是事件拦截

    `@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_MOVE) {
            y = (int) ev.getRawY();
            return true;
        }
        return super.onInterceptTouchEvent(ev);
    }`
    
  • 这里拦截了所有的move事件
  • 我们在ontouchEvent中处理

     `public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
        y = (int) event.getRawY();
        break;
    case MotionEvent.ACTION_MOVE:
        int min = (int) (event.getRawY() - y);
        scrollTo(0, Math.min(totalHeight, Math.max(0, getScrollY() - min)));
        y = (int) event.getRawY();
        break;
    case MotionEvent.ACTION_UP:
        scroller.startScroll(0, getScrollY(), 0, getPosition()
                * getMeasuredHeight() - getScrollY(), Math
                .abs(getPosition() * getMeasuredHeight() - getScrollY()));
        postInvalidate();
        break;
    }
    return true;
    }
    

    `

  • 我们使用的是Scroller完成的滚动,因此需要重写computeScroll

    `   @Override
    public void computeScroll() {
        // TODO Auto-generated method stub
        super.computeScroll();
        if (scroller.computeScrollOffset()) {
            scrollTo(0, scroller.getCurrY());
            postInvalidate();
        }
    }`
    
  • 接下来,我们计算我们当前显示的是第几个view

    `private int getPosition() {
        return (int) (getScrollY() / (getMeasuredHeight() + 0.0f) + 0.5f);
    }`
    
  • 在布局加载完的时候给我们的控件添加点击监听

        `@Override
    protected void onFinishInflate() {
        // TODO Auto-generated method stub
        super.onFinishInflate();
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            getChildAt(i).setOnClickListener(this);
        }
    }`
    
  • 对外面提供我们自己定义的监听接口

    `public interface OnClickItemListener {
        public void OnClickItem(View view);
    }
    
    public void setOnClickItemListener(OnClickItemListener listener) {
        this.listener = listener;
    }`
    
  • 在onClick的时候做出相应

      ` @Override
    public void onClick(View v) {
        int position = getPosition();
        View child = getChildAt(position);
        if (listener != null) {
            listener.OnClickItem(child);
        }
    }
    

    `

  • 源码如下

     `package com.example.viewgroup1;
    
    import android.app.Activity;
    import android.content.Context;
    import android.support.v4.view.ViewConfigurationCompat;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.ViewConfiguration;
    import android.view.ViewGroup;
    import android.widget.Scroller;
    import android.view.View.OnClickListener;
    
    public class MyViewGroup extends ViewGroup implements OnClickListener {
    
    private int y;
    private int totalHeight;
    private Scroller scroller;
    private OnClickItemListener listener;
    
    public MyViewGroup(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        scroller = new Scroller(getContext());
    }
    
    public MyViewGroup(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        // TODO Auto-generated constructor stub
    }
    
    public MyViewGroup(Context context) {
        this(context, null);
        // TODO Auto-generated constructor stub
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measureWidth(widthMeasureSpec),
                measureHeight(heightMeasureSpec));
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
        }
    }
    
    private int measureHeight(int heightMeasureSpec) {
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        int size = MeasureSpec.getSize(heightMeasureSpec);
        int result = 0;
        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = 400;
            int count = getChildCount();
            if (count > 0) {
                View view = getChildAt(0);
                view.measure(0, MeasureSpec.makeMeasureSpec(
                        Integer.MAX_VALUE >> 2, MeasureSpec.EXACTLY));
                result = view.getMeasuredHeight();
            }
            if (mode == MeasureSpec.AT_MOST) {
                // 当为warp_content的时候 给一个最小的默认值
                result = Math.min(result, size);
            }
        }
        return result;
    }
    
    private int measureWidth(int widthMeasureSpec) {
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        int result = 0;
        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = 400;
            Context ctx = getContext();
            if (ctx instanceof Activity) {
                Activity a = (Activity) ctx;
                result = a.getWindowManager().getDefaultDisplay().getWidth();
            }
            if (mode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
                Log.e("", size + "");
            }
        }
        return result;
    }
    
    @Override
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        // TODO Auto-generated method stub
        // super.measureChild(child, parentWidthMeasureSpec,
        // parentHeightMeasureSpec);
        int height = MeasureSpec.getSize(parentHeightMeasureSpec);
        child.measure(MeasureSpec.makeMeasureSpec(LayoutParams.MATCH_PARENT,
                MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height,
                MeasureSpec.EXACTLY));
    }
    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // zhihou zaizheli xunhuan diaoyong childde layout
        int count = getChildCount();
    
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            child.layout(0, i * child.getMeasuredHeight(), getMeasuredWidth(),
                    (i + 1) * child.getMeasuredHeight());
    
        }
        totalHeight = (getChildCount() - 1) * getMeasuredHeight();
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            y = (int) event.getRawY();
            break;
        case MotionEvent.ACTION_MOVE:
            int min = (int) (event.getRawY() - y);
            scrollTo(0, Math.min(totalHeight, Math.max(0, getScrollY() - min)));
            y = (int) event.getRawY();
            break;
        case MotionEvent.ACTION_UP:
            scroller.startScroll(0, getScrollY(), 0, getPosition()
                    * getMeasuredHeight() - getScrollY(), Math
                    .abs(getPosition() * getMeasuredHeight() - getScrollY()));
            postInvalidate();
            break;
        }
        return true;
    }
    
    private int getPosition() {
        return (int) (getScrollY() / (getMeasuredHeight() + 0.0f) + 0.5f);
    }
    
    @Override
    public void computeScroll() {
        // TODO Auto-generated method stub
        super.computeScroll();
        if (scroller.computeScrollOffset()) {
            scrollTo(0, scroller.getCurrY());
            postInvalidate();
        }
    }
    
    // 点击item的监听
    public interface OnClickItemListener {
        public void OnClickItem(View view);
    }
    
    public void setOnClickItemListener(OnClickItemListener listener) {
        this.listener = listener;
    }
    
    @Override
    protected void onFinishInflate() {
        // TODO Auto-generated method stub
        super.onFinishInflate();
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            getChildAt(i).setOnClickListener(this);
        }
    }
    
    @Override
    public void onClick(View v) {
        int position = getPosition();
        View child = getChildAt(position);
        if (listener != null) {
            listener.OnClickItem(child);
        }
    }
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_MOVE) {
            y = (int) ev.getRawY();
            return true;
        }
        return super.onInterceptTouchEvent(ev);
    }
    
    }
    

    `

  • 源码下载 http://download.csdn.net/detail/u013356254/9476805

总结,写自定义view的时候比较复杂的是测量和事件冲突

发布了33 篇原创文章 · 获赞 21 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/u013356254/article/details/51015409