可滑动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开发艺术探索

1 0
原创粉丝点击