可滑动Header控件的实现
来源:互联网 发布:淘宝品牌男装授权 编辑:程序博客网 时间:2024/05/16 05:43
最近在项目开发中经常遇到这样的需求:在一个Activity中,上半部分是一个高度比较大(屏幕高度的1/3或者1/2)的Header,下半部分是ListView或者是ViewPager(里面依然包含ListView)。如果Header不能向上活动,则在浏览ListView中的内容时,只能在很小的高度里面滑动,不能发挥大屏幕的优势。如果在滑动过程中,使Header也滑动则会造成外层ViewGroup与内层ListView滑动冲突。
本文从滑动冲突产生的原因、解决方案以及代码实现过程三个方面,介绍了可滑动Header控件的实现过程。
1.滑动冲突产生的原因
滑动冲突的原因很简单,就是一个原因:在界面中只要内外两层同时可以滑动,就会产生滑动冲突。常见的滑动冲突场景有两种:
a.外部View滑动方向与内部View滑动方向一致;
b.外部View滑动方向与内部View滑动方向不一致。
Android系统提供的可滑动控件有的已经解决了滑动冲突问题,例如:ViewPager;而大部分可滑动控件并没有处理滑动冲突,例如:ScrollView、ListView。
2.解决方案
要想解决滑动冲突,首先要对Android的事件传递机制有一个深入的理解,本文的重点是讲述解决滑动冲突,对Android的事件传递机制就不展开了。Android的MotionEvent是从外层View向内层View传递的,一旦MotionEvent被拦截,MotionEvent就交给拦截它的View处理。针对滑动冲突问题,外层滑动View在onInterceptTouchEvent()方法中根据业务逻辑的需要判断是否拦截MotionEvent,在onTouchEvent()方法中实现滑动效果;在滑动的过程中,使内外层View只有一个能拦截MotionEvent,这样子就解决了View的滑动冲突问题。
3.实现过程
外层滑动View的实现`
import android.annotation.TargetApi;import android.content.Context;import android.os.Build;import android.os.Handler;import android.os.Message;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.ViewConfiguration;import android.widget.LinearLayout;import java.util.NoSuchElementException;public class SlideHeaderView extends LinearLayout{ private static final String TAG = "SlideHeaderView"; private static final boolean DEBUG = true; /** * 滑动的风格类型,非覆盖 */ private StyleType mStyleType = StyleType.SlideOut; /** * 覆盖式、非覆盖式枚举 */ public enum StyleType { SlideOut, SlideIn // SlideOut:整个View滑动,整个View的位置发生改变 SlideIn:整个View的位置没有改变,改变View内HeaderView的高度 } public interface OnInterceptScrollListener { boolean isInterceptScrollUpward(MotionEvent event); boolean isInterceptScrollDownward(MotionEvent event); } public interface OnChangeHeaderStateListener { void onExpandHeader(); void onCollapseHeader(); } private View mHeader; private View mContent; private OnInterceptScrollListener mInterceptScrollListener; private OnChangeHeaderStateListener mChangeHeaderStateListener; // header的高度 单位:px private int mOriginalHeaderHeight = -1; private int mHeaderHeight; private int minHeaderHeight; public int mStatus = STATUS_EXPANDED; public static final int STATUS_EXPANDED = 1; public static final int STATUS_COLLAPSED = 2; public int mAutoScrollOrientation; private final int mSlideDownward = 1; //向下滑动 private final int mSlideUpward = 2; //向上滑动 private int mTouchSlop; // 分别记录上次滑动的坐标 private int mLastX = 0; private int mLastY = 0; // 分别记录上次滑动的坐标(onInterceptTouchEvent) private int mLastXIntercept = 0; private int mLastYIntercept = 0; // 用来控制滑动角度,仅当角度a满足如下条件才进行滑动:tan a = deltaX / deltaY > 2 private static final int TAN = 2; private boolean mIsSticky = true; private boolean mInitDataSucceed = false; private boolean mDisallowInterceptTouchEventOnHeader = true; private MarginLayoutParams mTopMarginLayoutParams; private int mTopMargin; private int maxTopMargin = -1; private int mAnimationSpeed = 20; private int mAnimationInterval = 10; public SlideHeaderView(Context context) { super(context); } public SlideHeaderView(Context context, AttributeSet attrs) { super(context, attrs); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) public SlideHeaderView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if (hasWindowFocus && (mHeader == null || mContent == null)) { initData(); } } private void initData() { int headerId= getResources().getIdentifier("sticky_header", "id", getContext().getPackageName()); int contentId = getResources().getIdentifier("sticky_content", "id", getContext().getPackageName()); if (headerId != 0 && contentId != 0) { mHeader = findViewById(headerId); mContent = findViewById(contentId); if (mOriginalHeaderHeight < 0) { mOriginalHeaderHeight = mHeader.getMeasuredHeight(); } mHeaderHeight = mOriginalHeaderHeight; if (maxTopMargin < 0) { maxTopMargin = mOriginalHeaderHeight; } mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); if (mHeaderHeight > 0) { mInitDataSucceed = true; } if (DEBUG) { Log.d(TAG, "mTouchSlop = " + mTouchSlop + "mHeaderHeight = " + mHeaderHeight); } } else { throw new NoSuchElementException("Did your view with id \"sticky_header\" or \"sticky_content\" exists?"); } } public void setOnInterceptScrollListener(OnInterceptScrollListener l) { mInterceptScrollListener = l; } public void setOnChangeHeaderStateListener(OnChangeHeaderStateListener l) { mChangeHeaderStateListener = l; } public void setAnimationSpeed(int speed) { mAnimationSpeed = speed; } public void setAnimationInterval(int interval) { mAnimationInterval = interval; } public void setMaxTopMargin(int margin) { maxTopMargin = margin; } public void setMinHeaderHeight(int height) { minHeaderHeight = height; } public void setStyleType (StyleType type) { mStyleType = type; } @Override public boolean onInterceptTouchEvent(MotionEvent event) { int intercepted = 0; int x = getX(event); int y = getY(event); Log.d(TAG, "onInterceptTouchEvent-->y = " + y); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { mLastXIntercept = x; mLastYIntercept = y; mLastX = x; mLastY = y; Log.d(TAG, "onInterceptTouchEvent-->mLastY = " + mLastY); intercepted = 0; break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; if (Math.abs(deltaY) <= Math.abs(deltaX)) { intercepted = 0; } else if (deltaY <= -mTouchSlop) { if (mInterceptScrollListener != null && mInterceptScrollListener.isInterceptScrollUpward(event)) { intercepted = 1; } } else if (deltaY >= mTouchSlop) { if (mInterceptScrollListener != null && mInterceptScrollListener.isInterceptScrollDownward(event)) { intercepted = 1; } }// Log.d(TAG, "onInterceptTouchEvent-->ACTION_MOVE-->intercepted = " + intercepted); break; } case MotionEvent.ACTION_UP: { intercepted = 0; mLastXIntercept = mLastYIntercept = 0; break; } default: break; } if (DEBUG) { Log.d(TAG, "intercepted=" + intercepted); } return intercepted != 0 && mIsSticky; } @Override public boolean onTouchEvent(MotionEvent event) { if (!mIsSticky) { return true; } int x = getX(event); int y = getY(event); Log.d(TAG, "onTouchEvent-->y = " + y); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { mTopMargin = ((MarginLayoutParams) getLayoutParams()).topMargin; break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; Log.d(TAG, "onTouchEvent-->deltaY = " + deltaY); if (DEBUG) {// Log.d(TAG, "mHeaderHeight=" + mHeaderHeight + " deltaY=" + deltaY + " mlastY=" + mLastY); } if (mStyleType == StyleType.SlideIn) { mHeaderHeight += deltaY; setHeaderHeight(mHeaderHeight); } else if (mStyleType == StyleType.SlideOut) { mTopMargin += deltaY; setTopMargin(); Log.d(TAG, "onTouchEvent-->ACTION_MOVE-->mTopMargin = " + mTopMargin); } break; } case MotionEvent.ACTION_UP: { // 这里做了下判断,当松开手的时候,会自动向两边滑动,具体向哪边滑,要看当前所处的位置 if (mStyleType == StyleType.SlideIn) { if (mHeaderHeight <= mOriginalHeaderHeight * 0.5) {// mStatus = STATUS_COLLAPSED; mAutoScrollOrientation = mSlideUpward;//向上滑动 } else {// mStatus = STATUS_EXPANDED; mAutoScrollOrientation = mSlideDownward;//向下滑动 } post(new AutoSlidingRunnable()); } else if (mStyleType == StyleType.SlideOut) { if (Math.abs(mTopMargin) <= mOriginalHeaderHeight * 0.5) {// mStatus = STATUS_EXPANDED; mAutoScrollOrientation = mSlideDownward;//向下滑动 } else {// mStatus = STATUS_COLLAPSED; mAutoScrollOrientation = mSlideUpward;//向上滑动 } post(new AutoSlidingRunnable()); } break; } default: break; } mLastX = x; mLastY = y; return true; } private int getX(MotionEvent event) { int x = 0; if (mStyleType == StyleType.SlideIn) { x = (int) event.getX(); } else if (mStyleType == StyleType.SlideOut) { x = (int) event.getRawX(); } return x; } private int getY(MotionEvent event) { int y = 0; if (mStyleType == StyleType.SlideIn) { y = (int) event.getY(); } else if (mStyleType == StyleType.SlideOut) { y = (int) event.getRawY(); } return y; } public void setOriginalHeaderHeight(int originalHeaderHeight) { mOriginalHeaderHeight = originalHeaderHeight; } public void setHeaderHeight(int height, boolean modifyOriginalHeaderHeight) { if (modifyOriginalHeaderHeight) { setOriginalHeaderHeight(height); } setHeaderHeight(height); } public void setHeaderHeight(int height) { if (!mInitDataSucceed) { initData(); } if (DEBUG) {// Log.d(TAG, "setHeaderHeight height=" + height); } if (height <= minHeaderHeight) { height = minHeaderHeight; } else if (height > mOriginalHeaderHeight) { height = mOriginalHeaderHeight; } if (height == minHeaderHeight) { mStatus = STATUS_COLLAPSED; if (mChangeHeaderStateListener != null) { mChangeHeaderStateListener.onCollapseHeader(); } } else { mStatus = STATUS_EXPANDED; if (mChangeHeaderStateListener != null) { mChangeHeaderStateListener.onExpandHeader(); } } if (mHeader != null && mHeader.getLayoutParams() != null) { mHeader.getLayoutParams().height = height; mHeader.requestLayout(); mHeaderHeight = height; } else { if (DEBUG) { Log.e(TAG, "null LayoutParams when setHeaderHeight"); } } } public void setTopMargin() { if (mTopMargin < -maxTopMargin) { mTopMargin = -maxTopMargin; } else if (mTopMargin > 0) { mTopMargin = 0; } if (mTopMargin == -maxTopMargin) { mStatus = STATUS_COLLAPSED; if (mChangeHeaderStateListener != null) { mChangeHeaderStateListener.onCollapseHeader(); } } else { mStatus = STATUS_EXPANDED; if (mChangeHeaderStateListener != null) { mChangeHeaderStateListener.onExpandHeader(); } }// Log.d(TAG, "setTopMargin: topMargin = " + mTopMargin); mTopMarginLayoutParams = (MarginLayoutParams) getLayoutParams(); mTopMarginLayoutParams.topMargin = mTopMargin; setLayoutParams(mTopMarginLayoutParams); } public int getHeaderHeight() { return mHeaderHeight; } public void setSticky(boolean isSticky) { mIsSticky = isSticky; } public void requestDisallowInterceptTouchEventOnHeader(boolean disallowIntercept) { mDisallowInterceptTouchEventOnHeader = disallowIntercept; } public int getHeaderStatus() { return mStatus; } private class AutoSlidingRunnable implements Runnable { public AutoSlidingRunnable() {} @Override public void run() { switch (mStyleType) { case SlideOut: if (mAutoScrollOrientation == mSlideDownward) { mTopMargin = mTopMargin + mAnimationSpeed; Log.d(TAG, "向下滑动: topMargin = " + mTopMargin); if (mTopMargin >= 0 ) { mTopMargin = 0; mHandler.sendEmptyMessage(100); mStatus = STATUS_EXPANDED; if (mChangeHeaderStateListener != null) { mChangeHeaderStateListener.onExpandHeader(); } return; } mHandler.sendEmptyMessage(100); } else if (mAutoScrollOrientation == mSlideUpward) { mTopMargin = mTopMargin - mAnimationSpeed; Log.d(TAG, "向上滑动: topMargin = " + mTopMargin); if (mTopMargin <= -maxTopMargin ) { mTopMargin = -maxTopMargin; mHandler.sendEmptyMessage(100); mStatus = STATUS_COLLAPSED; if (mChangeHeaderStateListener != null) { mChangeHeaderStateListener.onCollapseHeader(); } return; } mHandler.sendEmptyMessage(100); } postDelayed(this, mAnimationInterval); break; case SlideIn: if (mAutoScrollOrientation == mSlideDownward) { mHeaderHeight = mHeaderHeight + mAnimationSpeed; Log.d(TAG, "向下滑动: mHeaderHeight = " + mHeaderHeight); if (mHeaderHeight >= mOriginalHeaderHeight) { mHeaderHeight = mOriginalHeaderHeight; mHandler.sendEmptyMessage(101); mStatus = STATUS_EXPANDED; if (mChangeHeaderStateListener != null) { mChangeHeaderStateListener.onExpandHeader(); } return; } mHandler.sendEmptyMessage(101); } else if (mAutoScrollOrientation == mSlideUpward) { mHeaderHeight = mHeaderHeight - mAnimationSpeed; Log.d(TAG, "向上滑动: mHeaderHeight = " + mHeaderHeight); if (mHeaderHeight <= minHeaderHeight) { mHeaderHeight = minHeaderHeight; mHandler.sendEmptyMessage(101); mStatus = STATUS_COLLAPSED; if (mChangeHeaderStateListener != null) { mChangeHeaderStateListener.onCollapseHeader(); } return; } mHandler.sendEmptyMessage(101); } postDelayed(this, mAnimationInterval); break; default: break; } } } /** * Handler处理者 */ private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 100: mTopMarginLayoutParams = (MarginLayoutParams) getLayoutParams(); mTopMarginLayoutParams.topMargin = mTopMargin; setLayoutParams(mTopMarginLayoutParams); break; case 101: if (mHeader != null && mHeader.getLayoutParams() != null) { mHeader.getLayoutParams().height = mHeaderHeight; mHeader.requestLayout(); } else { if (DEBUG) { Log.e(TAG, "null LayoutParams when setHeaderHeight"); } } break; default: break; } } }; public void expandHeader() { mAutoScrollOrientation = mSlideDownward; post(new AutoSlidingRunnable()); }}
使用这种方式解决滑动冲突问题,只需重新外层滑动View,内层滑动View不用做任何处理。
在使用SlideHeaderView的Activity中需要实现SlideHeaderView.OnInterceptScrollListener接口
@Override public boolean isInterceptScrollUpward(MotionEvent event) { if (isNeedIntercept) { return true; } return false; } @Override public boolean isInterceptScrollDownward(MotionEvent event) { if (isNeedIntercept) { return true; } return false; }
xml文件的实现
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FFEEEFEF"> <SlideHeaderView android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:id="@+id/sticky_header" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout> <LinearLayout android:id="@+id/sticky_content" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FFEEEFEF"></android.support.v4.view.ViewPager> </LinearLayout> </SlideHeaderView></RelativeLayout>
过一段时间,写一个demo上传上来。
reference:Android开发艺术探索
- 可滑动Header控件的实现
- WPF下可编辑Header的Tab控件实现
- 这两天实现了两个可滑动控件的嵌套。
- 【Android】自定义控件实现可滑动的开关(switch)
- Android实现可左右滑动的选择控件
- 仿微信的列表滑动删除(一) 可滑动控件
- 可滑动可点击,不占全屏的自定义控件
- 一行代码实现去除ListView,RecycleView等可滑动控件的光晕效果
- 用gallery实现可滑动的Tab
- Android-->实现可滑动删除的Layout
- ionic实现可滑动的tab
- 用MPAndroidChart实现可滑动的柱状图
- 可滑动控件(ListView,WebView,ScrollView)实现弹性滑动或者取消弹性滑动(上下或左右滑动)
- 如何实现可动态调整隐藏header的listview
- 如何实现可动态调整隐藏header的listview
- 如何实现可动态调整隐藏header的listview
- 如何实现可动态调整隐藏header的listview
- 如何实现可动态调整隐藏header的listview
- DFS.H4403.等式成立
- peachfuzz
- unity网格合并后位置会变
- Intent传递数据
- The Marshtomp has seen it all before
- 可滑动Header控件的实现
- 【Dongle】【Java】基础(一)之输出
- 【Leecode】207Validate Binary Search Tree有效二叉搜索树
- swift LOG 输出
- (44)类的作用域
- C语言——分解质因数
- 关于SVM一篇比较全介绍的博文
- 有感
- Spring+Structs2+Hibernate实例