安卓折叠视图

安卓折叠视图

上一篇博客写的是侧滑菜单,这篇就写个会折叠的菜单,先上效果图:
这里写图片描述
这里写图片描述
这里写图片描述
好骚是不是= =

package xiaolin.widget;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

import xiaolin.utils.Utils;

/**
 * Created by Administrator on 2016/1/14.
 */
public class AFoldingPaneLayer extends ViewGroup {

    public static final String TAG = "AFoldingPaneLayer";
    public static final int MININERCEPTXVILOCITY = 8;   // 最小拦截速度(dp)
    public static final int MINTOUCHVILOCITY = 300;     // 最小触发速度(dp)
    private static final int drawerTime = 400;          // 动画时间

    private Scroller mScroller = null;
    private VelocityTracker velocity = null;
    private Paint paint = new Paint();
    private Paint mPaintShadow = new Paint();
    private Bitmap bitmapFull = null;
    private AFoldingListener listener = null;

    private int menuWidth = 0;        // 菜单宽度
    private boolean mIsOpenFolding = false;     // 是否显示抽屉
    private boolean isTouchRight = false;
    private boolean isBeingDragged = false;
    private boolean isUnableToDrad = false;
    private boolean mIsUpdateBitmap = true;
    private float lastInerceptX = 0;
    private float lastInerceptY = 0;
    private float lastTouchX = 0;
    private float firstTouchX= 0;
    private int mNumberOfFolds = 2;

    public AFoldingPaneLayer(Context context) {
        super(context);
        init(context);
    }

    public AFoldingPaneLayer(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public AFoldingPaneLayer(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context){
        mScroller = new Scroller(context);
        paint.setAntiAlias(true);
        paint.setDither(true);

        mPaintShadow.setColor(Color.BLACK);
        mPaintShadow.setStyle(Paint.Style.FILL);
    }

    @Override
    // 布局测量
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(width, height);
        menuWidth= (int)(width*0.75f);         // 菜单宽度
        if(bitmapFull != null){
            bitmapFull.recycle();
            bitmapFull = null;
        }
        bitmapFull = Bitmap.createBitmap(width+menuWidth, height, Bitmap.Config.ARGB_8888);
        mIsUpdateBitmap = true;
        int childCount = getChildCount();
        if(childCount < 1){
            return;
        }
        View menuView = getChildAt(0);
        // 合成菜单的测量宽度
        int menuWidthSpec = MeasureSpec.makeMeasureSpec(menuWidth, MeasureSpec.EXACTLY);
        menuView.measure(menuWidthSpec, heightMeasureSpec);
        for(int i=1; i<childCount; i++){
            View childView1 = getChildAt(i);
            childView1.measure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    /*布局,设置字控件的位置*/
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        if(childCount <1){
            return;
        }
        // 隐藏在左边的视图
        View menuView = getChildAt(0);
        // 设置子视图的位置,左上角坐标,宽高
        menuView.layout(-menuView.getMeasuredWidth(), 0, 0, menuView.getMeasuredHeight());

        int left = 0;
        for(int i=1; i<childCount; i++) {
            View childView = getChildAt(i);
            int w = childView.getMeasuredWidth();
            childView.layout(left, 0, left+w, childView.getMeasuredHeight());
            left += w;
        }
    }

    @Override
    // onDraw()函数会调用此函数
    public void computeScroll() {
        if(mScroller.computeScrollOffset()){
            int x = mScroller.getCurrX();
            scrollTo(x, 0);
            // 调用invalidate()无效
            postInvalidate();
            if(listener != null){
                listener.progress(-x/menuWidth);
            }
        }
    }

    /**
     * 矩阵变换
     * @param rect_src 源
     * @param rect_dst 目标
     * @param deep     深度
     * @param isScaleLeft 在左边还是在右边
     * @return
     */
    private Matrix calculate(Rect rect_src, Rect rect_dst, int deep, boolean isScaleLeft){
        float src[] = new float[]{
                rect_src.left, rect_src.top,      // 左上
                rect_src.right, rect_src.top,     // 右上
                rect_src.right, rect_src.bottom,  // 右下
                rect_src.left, rect_src.bottom    // 左下
        };
        float[] dst = null;
        if(isScaleLeft) {
            dst = new float[]{
                    rect_dst.left, rect_dst.top+deep,
                    rect_dst.right, rect_dst.top,
                    rect_dst.right, rect_dst.bottom,
                    rect_dst.left, rect_dst.bottom-deep
            };
        }else{
            dst = new float[]{
                    rect_dst.left, rect_dst.top,
                    rect_dst.right, rect_dst.top + deep,
                    rect_dst.right, rect_dst.bottom - deep,
                    rect_dst.left, rect_dst.bottom
            };
        }
        Matrix matrix = new Matrix();
        matrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1);
        return matrix;
    }

    /*绘制子视图*/
    @Override
    protected void dispatchDraw(Canvas canvas) {
        int x = getScrollX();
        if(x >= 0 || mNumberOfFolds==0){
            super.dispatchDraw(canvas);
            return;
        }
        canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
                | Paint.FILTER_BITMAP_FLAG));// 画布抗锯齿
        if(-x == menuWidth || mIsUpdateBitmap){
            bitmapFull.eraseColor(Color.argb(0, 0, 0, 0));
            // 将所有像素设为透明
            Canvas canvas1 = new Canvas(bitmapFull);
            canvas1.translate(menuWidth, 0);
            // 平移到正确位团置
            super.dispatchDraw(canvas1);
            // 把视图全部画到位图上
            if(mIsUpdateBitmap){
                mIsUpdateBitmap = false;
                /*画上阴影*/
            }
        }

        int height = getHeight();
        if(-x != menuWidth) {   /*以折叠形式绘制菜单*/
            float dstX = x;
            float dstW = Math.abs((float) x / mNumberOfFolds);
            float srcX = 0;
            float srcW = Math.abs((float) menuWidth / mNumberOfFolds);
            int depth = (int)Math.sqrt(Math.pow(srcW, 2)-Math.pow(dstW, 2))/6;// 勾股定理,再缩小几倍
            for (int i = 0; i < mNumberOfFolds; i++) {
                Rect rect_src = new Rect((int) srcX, 0, (int) (srcX + srcW), height);
                Rect rect_dst = new Rect((int) dstX, 0, (int) (dstX + dstW), height);
                Matrix matrix = calculate(rect_src, rect_dst, depth, i%2==1);
                canvas.save();
                canvas.clipRect(rect_dst);
                canvas.concat(matrix);
                canvas.drawBitmap(bitmapFull,0, 0, paint);
                if(i%2==1){
                    mPaintShadow.setAlpha(128-128*-x/menuWidth);
                    canvas.drawRect(rect_src, mPaintShadow);
                }
                canvas.restore();
                dstX += dstW;
                srcX += srcW;
            }
        }else{  /*直接绘制菜单*/
            canvas.save();
            canvas.clipRect(-menuWidth, 0, 0, height);
            super.dispatchDraw(canvas);
            canvas.restore();
        }

        /*绘制右边的主内容*/
        Rect rect_src = new Rect(0, 0, this.getWidth(), height);
        Rect rect_dst = new Rect(0, 0, this.getWidth()+x, height);
        Matrix m = calculate(rect_src, rect_dst, -x / 8, false);
        m.preTranslate(-menuWidth, 0);
        canvas.save();
        canvas.clipRect(rect_src);
        canvas.drawBitmap(bitmapFull, m, paint);
        canvas.restore();
    }

    /**
     * 只要您在onInterceptTouchEvent方法中返回false,
     * 每个后续的事件(从当前事件到最后ACTION_UP事件)将会先分发到onInterceptTouchEvent中,
     * 然后再交给目标子控件的onTouchEvent处理 (前提是子控件的onTouchEvent返回是true )。
     *
     * 如果在onInterceptTouchEvent返回true,
     * onInterceptTouchEvent方法中将不会收到后续的任何事件,
     * 目标子控件中除了ACTION_CANCEL外也不会接收所有这些后续事件,
     * 所有的后续事件将会被交付到你自己的onTouchEvent()方法中。
     * @param event
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        int action = event.getAction();
        if(action != 0){
            if(isBeingDragged){
                return true;
            }
            if(isUnableToDrad){
                return false;
            }
        }
        if(action == MotionEvent.ACTION_DOWN){
            lastInerceptX = event.getX();
            lastInerceptY = event.getY();
            isBeingDragged = false;
            isUnableToDrad = false;
            if(!mScroller.isFinished()){
                mScroller.abortAnimation();
                return true;
            }else if(mIsOpenFolding){
                if(event.getX()>menuWidth){
                    isBeingDragged = true;
                }
            }
        }else if(action == MotionEvent.ACTION_MOVE) {
            float x = event.getX();
            float y = event.getY();
            float dx = Math.abs(x-lastInerceptX);
            float dy = Math.abs(y-lastInerceptY);
            if(dy>dx) {  // 上下滑动
                isUnableToDrad = true;
            }else if(x<lastInerceptX && !mIsOpenFolding) { // 往左滑动&&已关窗
                isUnableToDrad = true;
            }else if(x>lastInerceptX && mIsOpenFolding){    // 往右滑动&&已开窗
                isUnableToDrad = true;
            }else if(dx>dy && dx> Utils.dpToPx(MININERCEPTXVILOCITY, getResources())){  // 往右并且大于一定速度
                isBeingDragged = true;
            }
            lastInerceptX = x;
            lastInerceptY = y;
        }
        return isBeingDragged;
    }

    // 触摸事件
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        float x = event.getX();
        if(velocity == null){
            mIsUpdateBitmap = true;
            velocity = VelocityTracker.obtain();
            lastTouchX = x;
            if(mIsOpenFolding && x>menuWidth){
                isTouchRight = true;
                firstTouchX = x;
            }else{
                isTouchRight = false;
            }
        }
        velocity.addMovement(event);
        if(action == MotionEvent.ACTION_DOWN){// 可能无此动作

        }else if(action == MotionEvent.ACTION_MOVE){ // 正在滑动
            float dx = x-lastTouchX;
            drawerViewBy((int)dx);
            lastTouchX = x;
        }else if(action == MotionEvent.ACTION_UP){
            if(isTouchRight && firstTouchX==x){
                closeFolding();
                velocity.recycle();
                velocity = null;
                return true;
            }else{
                velocity.computeCurrentVelocity(1000);
                if(velocity.getXVelocity() < -Utils.dpToPx(MINTOUCHVILOCITY, getResources())){
                    closeFolding();
                }else if(velocity.getXVelocity() > Utils.dpToPx(MININERCEPTXVILOCITY, getResources())){
                    openFolding();
                }else{
                    if(-getScrollX()>menuWidth/2){
                        openFolding();
                    }else{
                        closeFolding();
                    }
                }
                velocity.recycle();
                velocity = null;
            }
        }
        return true;
    }

    @Override
    // 按键监听
    public boolean dispatchKeyEvent(KeyEvent event) {
        int code = event.getKeyCode();
        int action = event.getAction();
        if(code==KeyEvent.KEYCODE_BACK && action==KeyEvent.ACTION_UP){
            if(mIsOpenFolding){
                closeFolding();
                return true;
            }
        }
        return super.dispatchKeyEvent(event);
    }

    // 滚动距离并判断范围
    private void drawerViewBy(int dx){
        dx = -dx;
        int x = getScrollX();
        if(x+dx>0){
            scrollTo(0, 0);
        }else if(x+dx < -menuWidth){
            scrollTo(-menuWidth, 0);
        }else {
            scrollTo(x + dx, 0);
        }
        if(listener != null){
            listener.progress(-getScrollX()/(float)menuWidth);
        }
    }

    // 设置窗格数量
    public void setNumberOfFolds(int numberOfFolds){
        this.mNumberOfFolds = numberOfFolds;
    }

    public int getNumberOfFolds(){
        return mNumberOfFolds;
    }

    // 关闭菜单
    public void closeFolding(){
        int x = getScrollX();
        mScroller.startScroll(x, 0, -x, 0, drawerTime);
        invalidate();
        mIsOpenFolding = false;
        if(listener != null) {
            listener.fold(mIsOpenFolding);
        }
    }

    // 打开菜单
    public void openFolding(){
        int x = getScrollX();
        mScroller.startScroll(x, 0, -menuWidth - x, 0, drawerTime);
        invalidate();
        mIsOpenFolding = true;
        if(listener != null){
            listener.fold(mIsOpenFolding);
        }
    }

    // 是否打开菜单
    public boolean isOpen(){
        return mIsOpenFolding;
    }

    // 设置监听
    public void setFoldingListener(AFoldingListener listener){
        this.listener = listener;
    }

    // 监听接口
    public interface AFoldingListener{
        public void fold(boolean isOpen);
        public void progress(float progress);// 0.0f-1.0f
    }

}
package xiaolin.utils;

import android.content.res.Resources;
import android.util.TypedValue;

/**
 * Created by Administrator on 2016/1/17.
 */
public class Utils {

    private Utils(){

    }

    /**
     * dp转为像素
     * @param dp
     * @param resources
     * @return
     */
    public static float dpToPx(float dp, Resources resources){
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.getDisplayMetrics());
    }
}

在xml里使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#ffffff"
    android:orientation="vertical">

    <xiaolin.widget.AFoldingPaneLayer
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/layout">

        <!-- 菜单部分 -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#ffffff"
            android:orientation="vertical">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:src="@drawable/img_bg"
                android:scaleType="centerCrop"/>

        </LinearLayout>

        <!-- 内容部分 -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/colorPrimary"
            android:orientation="vertical"
            android:gravity="center">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="小林"
                android:textColor="#ffffff"
                android:textSize="20sp"/>

        </LinearLayout>

    </xiaolin.widget.AFoldingPaneLayer>

</LinearLayout>

在Activity里使用

package xiaolin.widget;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        AFoldingPaneLayer layer = (AFoldingPaneLayer)findViewById(R.id.layout);
        layer.setNumberOfFolds(4);// 设置有四个窗格,默认有两个窗格
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值