
源码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
或关注微信公众号:口袋里的安卓
网友评论