轻量级--层叠的侧边栏

来源:互联网 发布:木马编程 编辑:程序博客网 时间:2024/05/19 19:33

github上的自定义view:

package com.mstarc.app.mstarchelper2.functions.home.widget;/** * Created by Administrator on 2017/4/13. */import android.content.Context;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.PorterDuff.Mode;import android.graphics.Rect;import android.graphics.Region.Op;import android.os.Build;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.animation.Animation;import android.view.animation.DecelerateInterpolator;import android.view.animation.Transformation;import android.widget.FrameLayout;import java.util.LinkedList;import java.util.Queue;/** * 轻量级侧边栏:分为菜单栏和内容栏。 * 仅支持从左边或右边开启侧边栏菜单,不支持同时,且滑开菜单栏的动画不能修改; * 实现方法:滑动的是内容栏,菜单栏不动。这个是使用Canvas.translate()配合动画来完成内容栏滑动效果的。 * 不过我们自己写的话,大多数会采用scrollTo()函数来完成。 */public class SlideHolder extends FrameLayout {    public final static int DIRECTION_LEFT = 1; // 左侧边栏    public final static int DIRECTION_RIGHT = -1; // 右侧边栏    protected final static int MODE_READY = 0; // 标记菜单栏还没有打开,可以滑动侧边栏    protected final static int MODE_SLIDE = 1; // 标记菜单栏正在划开侧边栏过程中...    protected final static int MODE_FINISHED = 2; // 标记菜单栏是否已经打开    private Bitmap mCachedBitmap; // 与内容栏的宽高相等的图片    private Canvas mCachedCanvas; // 画布,用于    private Paint mCachedPaint;    private View mMenuView; // 菜单栏    private int mMode = MODE_READY;    private int mDirection = DIRECTION_LEFT; // 从左边打开侧边栏或右边    private int mOffset = 0; // 内容栏的当前偏移量    private int mStartOffset; // 内容栏开始移动的位置的偏移量    private int mEndOffset; // 内容栏移动结束的位置的偏移量    private boolean mEnabled = true;    private boolean mInterceptTouch = true; // 标记是否允许侧边栏截获触摸手势    private boolean mAlwaysOpened = false; // 标记侧边栏是否是持续打开的,用于大屏幕的Pad    private boolean mDispatchWhenOpened = false; // 标记当侧边栏打开时,是否分发触摸手势    private Queue<Runnable> mWhenReady = new LinkedList<Runnable>(); // 打开/关闭菜单栏的线程对象集合    private OnSlideListener mListener; // 滑动菜单栏结束的回调    public SlideHolder(Context context) {        super(context);        initView();    }    public SlideHolder(Context context, AttributeSet attrs) {        super(context, attrs);        initView();    }    public SlideHolder(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        initView();    }    private void initView() {        mCachedPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG                | Paint.DITHER_FLAG);    }    @Override    public void setEnabled(boolean enabled) {        mEnabled = enabled;    }    @Override    public boolean isEnabled() {        return mEnabled;    }    /**     * 设置侧边栏打开方向     *     * @param direction     *            - direction in which SlideHolder opens. Can be:     *            DIRECTION_LEFT, DIRECTION_RIGHT     */    public void setDirection(int direction) {        closeImmediately();        mDirection = direction;    }    /**     * 设置允许截获触摸手势事件     *     * @param allow     *            - if false, SlideHolder won't react to swiping gestures (but     *            still will be able to work by manually invoking mathods)     */    public void setAllowInterceptTouch(boolean allow) {        mInterceptTouch = allow;    }    /**     * 判断是否允许侧边栏截获触摸手势事件     * @return     */    public boolean isAllowedInterceptTouch() {        return mInterceptTouch;    }    /**     * 判断是否允许侧边栏分发触摸手势事件     * @param dispatch     *            - if true, in open state SlideHolder will dispatch touch     *            events to main layout (in other words - it will be clickable)     */    public void setDispatchTouchWhenOpened(boolean dispatch) {        mDispatchWhenOpened = dispatch;    }    public boolean isDispatchTouchWhenOpened() {        return mDispatchWhenOpened;    }    /**     * 设置侧边栏总是打开,用于大屏幕的Pad设备     * @param opened     *            - if true, SlideHolder will always be in opened state (which     *            means that swiping won't work)     */    public void setAlwaysOpened(boolean opened) {        mAlwaysOpened = opened;        requestLayout();    }    /**     * 获取侧边栏菜单的偏移量     * @return     */    public int getMenuOffset() {        return mOffset;    }    public void setOnSlideListener(OnSlideListener lis) {        mListener = lis;    }    /**     * 菜单栏是否已经打开     * @return     */    public boolean isOpened() {        return mAlwaysOpened || mMode == MODE_FINISHED;    }    /**     * 菜单栏的开关     * @param immediately     */    public void toggle(boolean immediately) {        if (immediately) {            toggleImmediately();        } else {            toggle();        }    }    /**     * 菜单栏开关     */    public void toggle() {        if (isOpened()) {            close();        } else {            open();        }    }    /**     *  菜单栏立即开关     */    public void toggleImmediately() {        if (isOpened()) {            closeImmediately();        } else {            openImmediately();        }    }    /**     * 开启菜单栏     * @return     */    public boolean open() {        if (isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {            return false;        }        if (!isReadyForSlide()) {            mWhenReady.add(new Runnable() {                @Override                public void run() {                    open();                }            });            return true;        }        initSlideMode();        Animation anim = new SlideAnimation(mOffset, mEndOffset);        anim.setAnimationListener(mOpenListener);        startAnimation(anim);        invalidate();        return true;    }    /**     * 立即打开侧边栏菜单     * @return     */    public boolean openImmediately() {        if (isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {            return false;        }        if (!isReadyForSlide()) {            mWhenReady.add(new Runnable() {                @Override                public void run() {                    openImmediately();                }            });            return true;        }        mMenuView.setVisibility(View.VISIBLE);        mMode = MODE_FINISHED;        requestLayout();        if (mListener != null) {            mListener.onSlideCompleted(true);        }        return true;    }    /**     * 关闭菜单栏     * @return     */    public boolean close() {        if (!isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {            return false;        }        if (!isReadyForSlide()) {            mWhenReady.add(new Runnable() {                @Override                public void run() {                    close();                }            });            return true;        }        initSlideMode();        Animation anim = new SlideAnimation(mOffset, mEndOffset); // 关闭菜单栏的动画        anim.setAnimationListener(mCloseListener);        startAnimation(anim);        invalidate();        return true;    }    /**     * 快速关闭菜单栏     * @return     */    public boolean closeImmediately() {        if (!isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {            return false;        }        if (!isReadyForSlide()) { // 侧边栏还没有准备好滑动,则加入队列中等待执行关闭菜单栏            mWhenReady.add(new Runnable() {                @Override                public void run() {                    closeImmediately();                }            });            return true;        }        mMenuView.setVisibility(View.GONE); // 直接把菜单栏设为GONE(够快速了吧!!)        mMode = MODE_READY;        requestLayout();        if (mListener != null) {            mListener.onSlideCompleted(false);        }        return true;    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        final int parentLeft = 0;        final int parentTop = 0;        final int parentRight = r - l;        final int parentBottom = b - t;        View menu = getChildAt(0);        int menuWidth = menu.getMeasuredWidth();        if (mDirection == DIRECTION_LEFT) {            menu.layout(parentLeft, parentTop, parentLeft + menuWidth, parentBottom);        } else {            menu.layout(parentRight - menuWidth, parentTop, parentRight, parentBottom);        }        if (mAlwaysOpened) {            if (mDirection == DIRECTION_LEFT) {                mOffset = menuWidth;            } else {                mOffset = 0;            }        } else if (mMode == MODE_FINISHED) {            mOffset = mDirection * menuWidth;        } else if (mMode == MODE_READY) {            mOffset = 0;        }        View main = getChildAt(1);        main.layout(parentLeft + mOffset, parentTop, parentLeft + mOffset                + main.getMeasuredWidth(), parentBottom);        invalidate();        Runnable rn;        while ((rn = mWhenReady.poll()) != null) {            rn.run();        }    }    /**     * 是否准备好打开菜单栏或关闭菜单栏,即是否可以滑动??     * @return 返回false的唯一情况是:当前正在滑动菜单栏中...     */    private boolean isReadyForSlide() {        return (getWidth() > 0 && getHeight() > 0);    }    @Override    protected void onMeasure(int wSp, int hSp) {        mMenuView = getChildAt(0);        if (mAlwaysOpened) {            View main = getChildAt(1);            if (mMenuView != null && main != null) {                measureChild(mMenuView, wSp, hSp);                LayoutParams lp = (LayoutParams) main.getLayoutParams();                if (mDirection == DIRECTION_LEFT) {                    lp.leftMargin = mMenuView.getMeasuredWidth();                } else {                    lp.rightMargin = mMenuView.getMeasuredWidth();                }            }        }        super.onMeasure(wSp, hSp);    }    private byte mFrame = 0;    @Override    protected void dispatchDraw(Canvas canvas) {        try {            if (mMode == MODE_SLIDE) {                View main = getChildAt(1);                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {                    /*                     * On new versions we redrawing main layout only if it's                     * marked as dirty                     */                    if (main.isDirty()) {                        mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);                        main.draw(mCachedCanvas);                    }                } else {                    /*                     * On older versions we just redrawing our cache every 5th                     * frame                     */                    if (++mFrame % 5 == 0) {                        mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);                        main.draw(mCachedCanvas);                    }                }                /*                 * Draw only visible part of menu                 */                View menu = getChildAt(0);                final int scrollX = menu.getScrollX();                final int scrollY = menu.getScrollY();                canvas.save();                if (mDirection == DIRECTION_LEFT) {                    canvas.clipRect(0, 0, mOffset, menu.getHeight(), Op.REPLACE);                } else {                    int menuWidth = menu.getWidth();                    int menuLeft = menu.getLeft();                    canvas.clipRect(menuLeft + menuWidth + mOffset, 0, menuLeft                            + menuWidth, menu.getHeight());                }                canvas.translate(menu.getLeft(), menu.getTop());                canvas.translate(-scrollX, -scrollY);                menu.draw(canvas);                canvas.restore();                canvas.drawBitmap(mCachedBitmap, mOffset, 0, mCachedPaint);            } else {                if (!mAlwaysOpened && mMode == MODE_READY) {                    mMenuView.setVisibility(View.GONE);                }                super.dispatchDraw(canvas);            }        } catch (IndexOutOfBoundsException e) {            /*             * Possibility of crashes on some devices (especially on Samsung).             * Usually, when ListView is empty.             */        }    }    private int mHistoricalX = 0;    private boolean mCloseOnRelease = false;    /**     * 分发触摸手势事件,这个是触摸手势被识别进入的第一个函数     */    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        if (((!mEnabled || !mInterceptTouch) && mMode == MODE_READY) || mAlwaysOpened) {            return super.dispatchTouchEvent(ev);        }        if (mMode != MODE_FINISHED) {            onTouchEvent(ev);            if (mMode != MODE_SLIDE) {                super.dispatchTouchEvent(ev);            } else {                MotionEvent cancelEvent = MotionEvent.obtain(ev);                cancelEvent.setAction(MotionEvent.ACTION_CANCEL);                super.dispatchTouchEvent(cancelEvent);                cancelEvent.recycle();            }            return true;        } else {            final int action = ev.getAction();            Rect rect = new Rect();            View menu = getChildAt(0);            menu.getHitRect(rect);            if (!rect.contains((int) ev.getX(), (int) ev.getY())) {                if (action == MotionEvent.ACTION_UP && mCloseOnRelease && !mDispatchWhenOpened) {                    close();                    mCloseOnRelease = false;                } else {                    if (action == MotionEvent.ACTION_DOWN && !mDispatchWhenOpened) {                        mCloseOnRelease = true;                    }                    onTouchEvent(ev);                }                if (mDispatchWhenOpened) {                    super.dispatchTouchEvent(ev);                }                return true;            } else {                onTouchEvent(ev);                ev.offsetLocation(-menu.getLeft(), -menu.getTop());                menu.dispatchTouchEvent(ev);                return true;            }        }    }    /**     * 处理触摸手势事件     * @param ev     * @return     */    private boolean handleTouchEvent(MotionEvent ev) {        if (!mEnabled) {            return false;        }        float x = ev.getX();        if (ev.getAction() == MotionEvent.ACTION_DOWN) {            mHistoricalX = (int) x;            return true;        }        if (ev.getAction() == MotionEvent.ACTION_MOVE) {            float diff = x - mHistoricalX;            // 判断是可以认为是滑动手势的            if ((mDirection * diff > 50 && mMode == MODE_READY)                    || (mDirection * diff < -50 && mMode == MODE_FINISHED)) {                mHistoricalX = (int) x;                initSlideMode();            } else if (mMode == MODE_SLIDE) { // 正处于滑动过程中...                mOffset += diff;                mHistoricalX = (int) x;                if (!isSlideAllowed()) {                    finishSlide();                }            } else {                return false;            }        }        if (ev.getAction() == MotionEvent.ACTION_UP) {            if (mMode == MODE_SLIDE) {                finishSlide();            }            mCloseOnRelease = false;            return false;        }        return mMode == MODE_SLIDE;    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        boolean handled = handleTouchEvent(ev);        invalidate();        return handled;    }    /*     * 初始化侧边栏菜单的模式:打开和关闭菜单栏的时候都需要重新设置 侧边栏参数,     * 这个函数在配合onLayout()函数,就实现了侧边栏动画。     */    private void initSlideMode() {        mCloseOnRelease = false;        View v = getChildAt(1); // 获取内容栏        if (mMode == MODE_READY) { // 侧边栏菜单未打开时            mStartOffset = 0;            mEndOffset = mDirection * getChildAt(0).getWidth();        } else { // 侧边栏菜单界面已经打开后            mStartOffset = mDirection * getChildAt(0).getWidth();            mEndOffset = 0; // 内容界面最后位置的偏移量        }        mOffset = mStartOffset; // 设置当前内容栏的偏移量        if (mCachedBitmap == null || mCachedBitmap.isRecycled()                || mCachedBitmap.getWidth() != v.getWidth()) {            mCachedBitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(),                    Bitmap.Config.ARGB_8888);            mCachedCanvas = new Canvas(mCachedBitmap);        } else {            mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);        }        v.setVisibility(View.VISIBLE);        mCachedCanvas.translate(-v.getScrollX(), -v.getScrollY());        v.draw(mCachedCanvas);        mMode = MODE_SLIDE;        mMenuView.setVisibility(View.VISIBLE);    }    /*     * 是否允许滑动     */    private boolean isSlideAllowed() {        return (mDirection * mEndOffset > 0 && mDirection * mOffset < mDirection * mEndOffset                && mDirection * mOffset >= mDirection * mStartOffset)                || (mEndOffset == 0 && mDirection * mOffset > mDirection * mEndOffset                && mDirection * mOffset <= mDirection * mStartOffset);    }    /*     * 打开完毕的回调     */    private void completeOpening() {        mOffset = mDirection * mMenuView.getWidth(); // 设置当前内容栏的偏移量        requestLayout();        post(new Runnable() {            @Override            public void run() {                mMode = MODE_FINISHED;                mMenuView.setVisibility(View.VISIBLE);            }        });        if (mListener != null) {            mListener.onSlideCompleted(true);        }    }    /**     * 以动画方式打开菜单栏的回调函数     */    private Animation.AnimationListener mOpenListener = new Animation.AnimationListener() {        @Override        public void onAnimationStart(Animation animation) {        }        @Override        public void onAnimationRepeat(Animation animation) {        }        @Override        public void onAnimationEnd(Animation animation) {            completeOpening();        }    };    /**     * 完成关闭菜单栏的回调     */    private void completeClosing() {        mOffset = 0;        requestLayout();        post(new Runnable() {            @Override            public void run() {                mMode = MODE_READY;                mMenuView.setVisibility(View.GONE);            }        });        if (mListener != null) {            mListener.onSlideCompleted(false);        }    }    /**     * 关闭菜单栏的动画回调     */    private Animation.AnimationListener mCloseListener = new Animation.AnimationListener() {        @Override        public void onAnimationStart(Animation animation) {        }        @Override        public void onAnimationRepeat(Animation animation) {        }        @Override        public void onAnimationEnd(Animation animation) {            completeClosing();        }    };    /*     * 结束滑动     */    private void finishSlide() {        if (mDirection * mEndOffset > 0) { // 菜单栏在左侧            // 如果当前滑动距离大于结束距离的一般,则认为是滑动操作            if (mDirection * mOffset > mDirection * mEndOffset / 2) {                if (mDirection * mOffset > mDirection * mEndOffset) { // 防止滑过界                    mOffset = mEndOffset;                }                // 打开菜单栏动画                Animation anim = new SlideAnimation(mOffset, mEndOffset);                anim.setAnimationListener(mOpenListener);                startAnimation(anim);            } else {                if (mDirection * mOffset < mDirection * mStartOffset) { // 防止滑过界                    mOffset = mStartOffset;                }                // 关闭菜单栏动画                Animation anim = new SlideAnimation(mOffset, mStartOffset);                anim.setAnimationListener(mCloseListener);                startAnimation(anim);            }        } else { // 菜单栏在右侧            if (mDirection * mOffset < mDirection * mStartOffset / 2) {                if (mDirection * mOffset < mDirection * mEndOffset) {                    mOffset = mEndOffset;                }                Animation anim = new SlideAnimation(mOffset, mEndOffset);                anim.setAnimationListener(mCloseListener);                startAnimation(anim);            } else {                if (mDirection * mOffset > mDirection * mStartOffset) {                    mOffset = mStartOffset;                }                Animation anim = new SlideAnimation(mOffset, mStartOffset);                anim.setAnimationListener(mOpenListener);                startAnimation(anim);            }        }    }    /*     * 偏移动画     */    private class SlideAnimation extends Animation {        private static final float SPEED = 0.6f;        private float mStart;        private float mEnd;        public SlideAnimation(float fromX, float toX) {            mStart = fromX;            mEnd = toX;            setInterpolator(new DecelerateInterpolator());            float duration = Math.abs(mEnd - mStart) / SPEED;            setDuration((long) duration);        }        @Override        protected void applyTransformation(float interpolatedTime, Transformation t) {            super.applyTransformation(interpolatedTime, t);            float offset = (mEnd - mStart) * interpolatedTime + mStart;            mOffset = (int) offset;            postInvalidate();        }    }    public static interface OnSlideListener {        public void onSlideCompleted(boolean opened);    }}

activity布局:

<?xml version="1.0" encoding="utf-8"?><com.mstarc.app.mstarchelper2.functions.home.widget.SlideHolder    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/slideHolder"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.mstarc.app.mstarchelper2.functions.home.ui.HomeActivity">    <ScrollView        android:layout_width="200dp"        android:layout_height="match_parent">        <include layout="@layout/home_side"></include>    </ScrollView>    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="match_parent">        <ImageView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="@drawable/home_top_bar_bg"            />        <LinearLayout            android:id="@+id/ly_param_bar"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:orientation="vertical">            <com.mstarc.app.mstarchelper2.common.base.TopTitleLayout                android:id="@+id/ttl_home_title"                android:layout_width="match_parent"                android:layout_height="wrap_content"/>            <LinearLayout                android:layout_width="match_parent"                android:layout_height="126px"                android:layout_alignParentTop="true"                android:background="@color/trans"                android:orientation="horizontal"                >                <LinearLayout                    android:layout_width="wrap_content"                    android:layout_height="wrap_content"                    android:layout_weight="1"                    android:gravity="center"                    android:orientation="vertical">                    <TextView                        android:id="@+id/tv_home_battery_left"                        android:layout_width="wrap_content"                        android:layout_height="wrap_content"                        android:text="10%"                        android:textColor="@color/white"                        android:textSize="@dimen/home_top_params_size"/>                    <TextView                        android:layout_width="wrap_content"                        android:layout_height="wrap_content"                        android:text="电量"                        android:textColor="@color/white"                        android:textSize="@dimen/home_top_label_size"/>                </LinearLayout>                <LinearLayout                    android:layout_width="wrap_content"                    android:layout_height="wrap_content"                    android:layout_weight="1"                    android:gravity="center"                    android:orientation="vertical">                    <ImageView                        android:id="@+id/iv_home_blue_connect"                        android:layout_width="wrap_content"                        android:layout_height="70px"                        android:src="@drawable/home_bar_icon_connected"/>                    <TextView                        android:layout_width="wrap_content"                        android:layout_height="wrap_content"                        android:layout_marginTop="3px"                        android:text="已连接"                        android:textColor="@color/white"                        android:textSize="@dimen/home_top_label_size"/>                </LinearLayout>                <LinearLayout                    android:layout_width="wrap_content"                    android:layout_height="wrap_content"                    android:layout_weight="1"                    android:gravity="center"                    android:orientation="vertical">                    <TextView                        android:id="@+id/tv_home_step"                        android:layout_width="wrap_content"                        android:layout_height="wrap_content"                        android:text="100000"                        android:textColor="@color/white"                        android:textSize="@dimen/home_top_params_size"/>                    <TextView                        android:layout_width="wrap_content"                        android:layout_height="wrap_content"                        android:text="步数"                        android:textColor="@color/white"                        android:textSize="@dimen/home_top_label_size"/>                </LinearLayout>            </LinearLayout>        </LinearLayout>        <com.zhy.autolayout.AutoLinearLayout            android:id="@+id/test"            android:layout_width="match_parent"            android:layout_height="@dimen/home_lunbo_height"            android:layout_below="@+id/ly_param_bar"            android:background="@color/black"            android:orientation="vertical">        </com.zhy.autolayout.AutoLinearLayout>        <GridView            android:id="@+id/gv_fun"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_below="@+id/test"            android:numColumns="3"            />    </RelativeLayout></com.mstarc.app.mstarchelper2.functions.home.widget.SlideHolder>
activity:
SlideHolder mSlideHolder;mSlideHolder = (SlideHolder) findViewById(R.id.slideHolder);
mSlideHolder.toggle();


0 0
原创粉丝点击