粘性控件
- 产生粘性控件很简单,我们只需要进行相关的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