美文网首页
Android自定义控件:自定义分隔栏控件

Android自定义控件:自定义分隔栏控件

作者: 超级绿茶 | 来源:发表于2020-05-12 11:20 被阅读0次
Screenshot_1593337284.png

源码SplitLayout.java

package com.example.splitlayoutdemo;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.util.AttributeSet;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

public class SplitLayout extends ViewGroup {

    static final String TAG = "SplitLayout";
    public static final int HORIZONTAL = 0;
    public static final int VERTICAL = 1;

    private static final float INVAID_SPLITPOSITION = Float.MIN_VALUE;
    private static final int DEFAULT_SPLIT_HANDLE_SIZE_DP = 15;//这个是控制上下拖动的 拖动时线的粗细.前提是把拖拽图片隐藏
    private static final int DEFAULT_CHILD_MIN_SIZE_DP = 1;//这个是各个方向拉伸 缩小的那边的最小值
    public static final int[] PRESSED_STATE_SET = {android.R.attr.state_pressed};
    public static final int[] EMPTY_STATE_SET = {};

    private int mOrientation;

    public float mSplitFraction;
    private float mSplitPosition = INVAID_SPLITPOSITION;

    public Drawable mHandleDrawable;
    private int mHandleSize;
    private boolean mHandleHapticFeedback;

    private View mChild0, mChild1;
    private float mLastMotionX, mLastMotionY;
    private int mChildMinSize;
    public int mWidth, mHeight;

    private boolean mIsDragging = false;
    private Callback touchCallback;

    public SplitLayout(Context context) {
        this(context, null, 0);
    }

    public SplitLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    private Context ctx;        //新增
    private AttributeSet att;  //新增
    private int def;  //新增

    public SplitLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ctx = context;  //新增
        att = attrs;   //新增
        def = defStyleAttr;//新增
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SplitLayout, defStyleAttr, 0);
        mOrientation = a.getInteger(R.styleable.SplitLayout_splitOrientation, HORIZONTAL);
        mChildMinSize = a.getDimensionPixelSize(R.styleable.SplitLayout_splitChildMinSize,
                dp2px(DEFAULT_CHILD_MIN_SIZE_DP));

        //defValue是控制左右比例,左边的占比
        mSplitFraction = a.getFloat(R.styleable.SplitLayout_splitFraction, 0.5f);
        checkSplitFraction();
        mHandleDrawable = a.getDrawable(R.styleable.SplitLayout_splitHandleDrawable);
        if (mHandleDrawable == null) {
            StateListDrawable stateListDrawable = new StateListDrawable();
            stateListDrawable.addState(new int[]{}, new ColorDrawable(Color.TRANSPARENT));
            mHandleDrawable = stateListDrawable;
        }
        mHandleDrawable.setCallback(this);
        mHandleSize = Math.round(a.getDimension(R.styleable.SplitLayout_splitHandleSize, 0f));
        if (mHandleSize <= 0) {
            mHandleSize = mOrientation == HORIZONTAL ?
                    mHandleDrawable.getIntrinsicWidth() : mHandleDrawable.getIntrinsicHeight();
        }
        if (mHandleSize <= 0) {
            mHandleSize = dp2px(DEFAULT_SPLIT_HANDLE_SIZE_DP);
        }
        mHandleHapticFeedback = a.getBoolean(R.styleable.SplitLayout_splitHandleHapticFeedback, false);
        a.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
        checkChildren();
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthSize > 0 && heightSize > 0) {
            mWidth = widthSize;
            mHeight = heightSize;  //修改   获取到的heightSize值有问题  特别小  应该是系统bug 所以外部自己重新赋值
            setMeasuredDimension(mWidth, mHeight);//修改

            checkSplitPosition();
            final int splitPosition = Math.round(mSplitPosition);
            if (mOrientation == VERTICAL) {
                mChild0.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(splitPosition - mHandleSize / 2, MeasureSpec.EXACTLY));
                mChild1.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(heightSize - splitPosition - mHandleSize / 2, MeasureSpec.EXACTLY));
            } else {
                mChild0.measure(MeasureSpec.makeMeasureSpec(splitPosition - mHandleSize / 2, MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
                mChild1.measure(
                        MeasureSpec.makeMeasureSpec(widthSize - splitPosition - mHandleSize / 2, MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
            }
        }
    }

    //初始化会调用,拖拽布局之后也会调用
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int w = r - l;
        int h = b - t;
        final int splitPosition = Math.round(mSplitPosition);
        if (mOrientation == VERTICAL) {
            mChild0.layout(0, 0, w, splitPosition - mHandleSize / 2);
            mChild1.layout(0, splitPosition + mHandleSize / 2, w, h);
        } else {
            mChild0.layout(0, 0, splitPosition - mHandleSize / 2, h);
            mChild1.layout(splitPosition + mHandleSize / 2, 0, w, h);
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (mHandleDrawable == null) {
            return false;
        }
        final int action = ev.getAction();
        float x = ev.getX();
        float y = ev.getY();
        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN: {
                if (isUnderSplitHandle(x, y)) {
                    if (mHandleHapticFeedback) {
                        performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
                    }
                    if (mHandleDrawable != null) {
                        mHandleDrawable.setState(PRESSED_STATE_SET);//新增
                    }
                    mIsDragging = true;
                    getParent().requestDisallowInterceptTouchEvent(true);
                    invalidate();
                    if (touchCallback != null) touchCallback.onPress(SplitLayout.this);
                } else {
                    mIsDragging = false;
                }
                mLastMotionX = x;
                mLastMotionY = y;
                break;
            }
            case MotionEvent.ACTION_MOVE:
                if (mIsDragging) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                    if (mOrientation == VERTICAL) {
                        float deltaY = y - mLastMotionY;
                        updateSplitPositionWithDelta(deltaY);
                    } else {
                        float deltaX = x - mLastMotionX;
                        updateSplitPositionWithDelta(deltaX);
                    }
                    mLastMotionX = x;
                    mLastMotionY = y;
                    if (touchCallback != null) touchCallback.onMove(SplitLayout.this);
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (mIsDragging) {
                    if (mHandleDrawable != null) {
                        mHandleDrawable.setState(EMPTY_STATE_SET);
                    }
                    if (mOrientation == VERTICAL) {
                        float deltaY = y - mLastMotionY;
                        updateSplitPositionWithDelta(deltaY);
                    } else {
                        float deltaX = x - mLastMotionX;
                        updateSplitPositionWithDelta(deltaX);
                    }
                    mLastMotionX = x;
                    mLastMotionY = y;
                    mIsDragging = false;
                    if (touchCallback != null) touchCallback.onRelease(SplitLayout.this);
                }
                break;
        }
        return mIsDragging;
    }

    private boolean isUnderSplitHandle(float x, float y) {
        int halfSize = mHandleSize == 0 ? 0 : mHandleSize / 2;
        if (mOrientation == VERTICAL) {
            return y >= (mSplitPosition - halfSize) && y <= (mSplitPosition + halfSize);
        } else {
            return x >= (mSplitPosition - halfSize) && x <= (mSplitPosition + halfSize);
        }
    }

    private void updateSplitPositionWithDelta(float delta) {
        mSplitPosition = mSplitPosition + delta;
        checkSplitPosition();
        requestLayout();
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (mSplitPosition != INVAID_SPLITPOSITION && mHandleDrawable != null) {
            final int splitPosition = Math.round(mSplitPosition);
            if (mOrientation == VERTICAL) {
                mHandleDrawable.setBounds(0, splitPosition - mHandleSize / 2, mWidth, splitPosition + mHandleSize / 2);
            } else {
                mHandleDrawable.setBounds(splitPosition - mHandleSize / 2, 0, splitPosition + mHandleSize / 2, mHeight);
            }
            mHandleDrawable.draw(canvas);
        }
    }

    private void checkSplitFraction() {
        if (mSplitFraction < 0) {
            mSplitFraction = 0;
        } else if (mSplitFraction > 1) {
            mSplitFraction = 1;
        }
    }

    private void checkSplitPosition() {
        if (mOrientation == VERTICAL) {
            if (mSplitPosition == INVAID_SPLITPOSITION) {
                mSplitPosition = mHeight * mSplitFraction;
            }
            final int min = mChildMinSize + mHandleSize / 2;
            if (mSplitPosition < min) {
                mSplitPosition = min;
            } else {
                final int max = mHeight - mChildMinSize - mHandleSize / 2;
                if (mSplitPosition > max) {
                    mSplitPosition = max;
                }
            }
        } else {
            if (mSplitPosition == INVAID_SPLITPOSITION) {
                mSplitPosition = mWidth * mSplitFraction;
            }
            final int min = mChildMinSize + mHandleSize / 2;
            if (mSplitPosition < min) {
                mSplitPosition = min;
            } else {
                final int max = mWidth - mChildMinSize - mHandleSize / 2;
                if (mSplitPosition > max) {
                    mSplitPosition = max;
                }
            }
        }
    }

    private void checkChildren() {
        if (getChildCount() == 2) {
            mChild0 = getChildAt(0);
            mChild1 = getChildAt(1);
        } else {
            throw new IllegalStateException("SplitLayout ChildCount must be 2.");
        }
    }

    @Override
    public void jumpDrawablesToCurrentState() {
        super.jumpDrawablesToCurrentState();
        if (mHandleDrawable != null) {
            mHandleDrawable.jumpToCurrentState();
        }
    }

    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        updateSplitPositionWithDelta(0);
    }

    @Override
    protected boolean verifyDrawable(Drawable who) {
        return super.verifyDrawable(who) || who == mHandleDrawable;
    }

    private int dp2px(float dp) {
        return (int) (dp * getContext().getResources().getDisplayMetrics().density + 0.5f);
    }

    public int getSplitPosition() {
        return Math.round(mSplitPosition);
    }

    public View getChild0() {
        return mChild0;
    }

    public View getChild1() {
        return mChild1;
    }

    public void setCallback(Callback callback) {
        this.touchCallback = callback;
    }

    public interface Callback {
        void onPress(SplitLayout splitLayout);

        void onMove(SplitLayout splitLayout);

        void onRelease(SplitLayout splitLayout);
    }
}

自定义分隔条Drawable - splite_div.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="false">
        <layer-list>
            <item>
                <shape>
                    <size android:width="15dp" android:height="15dp"/>
                    <solid android:color="@android:color/holo_blue_light" />
                </shape>
            </item>
            <item android:bottom="5dp" android:left="5dp" android:right="5dp" android:top="5dp">
                <shape>
                    <stroke android:width="0.5dp" android:color="@android:color/holo_orange_light" />
                    <corners android:radius="2.5dp" />
                    <size android:height="5dp" android:width="5dp"/>
                    <solid android:color="@android:color/holo_orange_dark" />
                </shape>
            </item>
        </layer-list>
    </item>
    <item android:state_pressed="true">
        <layer-list>
            <item>
                <shape>
                    <size android:width="15dp" android:height="15dp"/>
                    <solid android:color="@android:color/holo_blue_dark" />
                </shape>
            </item>
            <item android:bottom="5dp" android:left="5dp" android:right="5dp" android:top="5dp">
                <shape>
                    <stroke android:width="0.5dp" android:color="@android:color/white" />
                    <corners android:radius="2.5dp" />
                    <size android:width="5dp" android:height="5dp" />
                    <solid android:color="@android:color/darker_gray" />
                </shape>
            </item>
        </layer-list>
    </item>
</selector>

创建资源参数文件 values/splitlayout.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SplitLayout">
        <attr name="splitOrientation" format="enum">
            <enum name="horizontal" value="0"/>
            <enum name="vertical" value="1"/>
        </attr>
        <attr name="splitChildMinSize" format="dimension"/>
        <attr name="splitFraction" format="float"/>
        <attr name="splitHandleDrawable" format="reference"/>
        <attr name="splitHandleSize" format="dimension"/>
        <attr name="splitHandleHapticFeedback" format="boolean"/>
    </declare-styleable>
</resources>

MainActivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        splitTop.setCallback(object : SplitLayout.Callback {
            override fun onPress(splitLayout: SplitLayout) {
                tvTop.text = getSplitInfo(splitLayout, 0)
            }

            override fun onRelease(splitLayout: SplitLayout) {
                tvTop.text = getSplitInfo(splitLayout, 0)
            }

            override fun onMove(splitLayout: SplitLayout) {
                tvTop.text = getSplitInfo(splitLayout, 0)
            }
        })
        splitBottom.setCallback(object : SplitLayout.Callback {
            override fun onPress(splitLayout: SplitLayout) {
                tvLeft.text = getSplitInfo(splitLayout, 0)
                tvRight.text = getSplitInfo(splitLayout, 1)
            }

            override fun onRelease(splitLayout: SplitLayout) {
                tvLeft.text = getSplitInfo(splitLayout, 0)
                tvRight.text = getSplitInfo(splitLayout, 1)
            }

            override fun onMove(splitLayout: SplitLayout) {
                tvLeft.text = getSplitInfo(splitLayout, 0)
                tvRight.text = getSplitInfo(splitLayout, 1)
            }

        })
    }

    private fun getSplitInfo(splitLayout: SplitLayout, which: Int): String {
        val size = when (which) {
            0 -> {
                val width = splitLayout.child0.width
                val height = splitLayout.child0.height
                "width:$width\nheight:$height\n"
            }
            1 -> {
                val width = splitLayout.child1.width
                val height = splitLayout.child1.height
                "width:$width\nheight:$height\n"
            }
            else -> ""
        }
        val position = splitLayout.splitPosition
        return size + "Split:$position"
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.splitlayoutdemo.SplitLayout
        android:id="@+id/splitTop"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:splitHandleDrawable="@drawable/splite_div"
        app:splitOrientation="vertical">

        <TextView
            android:id="@+id/tvTop"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="1" />

        <com.example.splitlayoutdemo.SplitLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/splitBottom"
            app:splitHandleDrawable="@drawable/splite_div"
            app:splitOrientation="horizontal">

            <TextView
                android:id="@+id/tvLeft"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="2" />

            <TextView
                android:id="@+id/tvRight"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="3" />
        </com.example.splitlayoutdemo.SplitLayout>
    </com.example.splitlayoutdemo.SplitLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

点击链接加入QQ群聊:https://jq.qq.com/?_wv=1027&k=5z4fzdT
或关注微信公众号:口袋里的安卓

相关文章

网友评论

      本文标题:Android自定义控件:自定义分隔栏控件

      本文链接:https://www.haomeiwen.com/subject/xhavnhtx.html