package com.mixiaoxiao.library.splitlayout; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.util.AttributeSet; import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import com.hmi.autotest.R; /** * SplitLayout github/Mixiaoxiao * * @author Mixiaoxiao 2016/08/18 */ public class SplitLayout extends ViewGroup { static final String TAG = "SplitLayout"; static final boolean DEBUG = true; 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 = 16; private static final int DEFAULT_CHILD_MIN_SIZE_DP = 32; private static final int[] PRESSED_STATE_SET = { android.R.attr.state_pressed }; private static final int[] EMPTY_STATE_SET = {}; private int mOrientation; private float mSplitFraction; private float mSplitPosition = INVAID_SPLITPOSITION; private Drawable mHandleDrawable; private int mHandleSize; private boolean mHandleHapticFeedback; private View mChild0, mChild1; private float mLastMotionX, mLastMotionY; private int mChildMinSize; private int mWidth, mHeight; private boolean mIsDragging = false; private Bitmap[] cachedBitmapArray = new Bitmap[2]; private Bitmap cachedBitmap; private Canvas cachedCanvas; // 使用双缓冲方式绘制拖动中的View变化,提高效率 private Paint mPaint = new Paint(); private int dragForgroundColor = Color.argb(0.88f, 1f, 1f, 1f); public SplitLayout(Context context) { this(context, null, 0); } public SplitLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SplitLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, 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)); 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[] { android.R.attr.state_pressed }, new ColorDrawable(0x990288d1)); stateListDrawable.addState(new int[] {}, new ColorDrawable(Color.TRANSPARENT)); stateListDrawable.setEnterFadeDuration(150); stateListDrawable.setExitFadeDuration(150); 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); dragForgroundColor = a.getColor(R.styleable.SplitLayout_splitDragForgroundColor, dragForgroundColor); a.recycle(); mPaint.setColor(dragForgroundColor); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // super.onMeasure(widthMeasureSpec, heightMeasureSpec); checkChildren(); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSize > 0 && heightSize > 0) { mWidth = widthSize; mHeight = heightSize; setMeasuredDimension(widthSize, heightSize); 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)); } // 初始化双缓冲对应的Bitmap和canvas cachedBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); cachedCanvas = new Canvas(cachedBitmap); } else { throw new IllegalStateException("SplitLayout with or height must not be MeasureSpec.UNSPECIFIED"); } } @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) { 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); } mHandleDrawable.setState(PRESSED_STATE_SET); mIsDragging = true; getParent().requestDisallowInterceptTouchEvent(true); getBitmapFromChildView(); // 截屏当前两个子View的图片,用于在拖动过程中的 invalidate(); } else { mIsDragging = false; } mLastMotionX = x; mLastMotionY = y; break; } case MotionEvent.ACTION_MOVE: if (mIsDragging) { getParent().requestDisallowInterceptTouchEvent(true); if (mOrientation == VERTICAL) { float deltaY = y - mLastMotionY; onlyUpdateSplitPosition(deltaY); // updateSplitPositionWithDelta(deltaY); postInvalidate(); // 使用子View的截图重绘界面 } else { float deltaX = x - mLastMotionX; onlyUpdateSplitPosition(deltaX); // updateSplitPositionWithDelta(deltaX); postInvalidate(); } mLastMotionX = x; mLastMotionY = y; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (mIsDragging) { 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; } break; } return mIsDragging; } private boolean isUnderSplitHandle(float x, float y) { if (mOrientation == VERTICAL) { return y >= (mSplitPosition - mHandleSize / 2) && y <= (mSplitPosition + mHandleSize / 2); } else { return x >= (mSplitPosition - mHandleSize / 2) && x <= (mSplitPosition + mHandleSize / 2); } } private void updateSplitPositionWithDelta(float delta) { mSplitPosition = mSplitPosition + delta; checkSplitPosition(); requestLayout(); } private void onlyUpdateSplitPosition(float delta) { mSplitPosition = mSplitPosition + delta; checkSplitPosition(); } @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); } Log.d("SplitLayout", "dispatchDraw"); if (mIsDragging) { // 开始拖动 // 获取两个子View的截图Bitmap cachedCanvas.drawColor(getContext().getColor(android.R.color.transparent), PorterDuff.Mode.CLEAR); if (cachedBitmapArray != null) { Rect child0Rect = new Rect(); Rect child1Rect = new Rect(); if (mOrientation == VERTICAL) { child0Rect.set(0, 0, getWidth(), (int) (mSplitPosition - mHandleSize / 2)); child1Rect.set(0, (int) (mSplitPosition + mHandleSize / 2), getWidth(), getHeight()); } else { child0Rect.set(0, 0, (int) (mSplitPosition - mHandleSize / 2), getHeight()); child1Rect.set((int) (mSplitPosition + mHandleSize / 2), 0, getWidth(), getHeight()); } // cachedCanvas.drawBitmap(cachedBitmapArray[0], null, child0Rect, null); cachedCanvas.drawRect(child0Rect, mPaint); cachedCanvas.drawBitmap(((BitmapDrawable)getContext().getDrawable(R.drawable.icon_app)).getBitmap(), child0Rect.centerX() - (getContext().getDrawable(R.drawable.icon_app).getBounds().width()/2), child0Rect.centerY() - (getContext().getDrawable(R.drawable.icon_app).getBounds().height()/2), null); // cachedCanvas.drawBitmap(cachedBitmapArray[1], null, child1Rect, null); cachedCanvas.drawRect(child1Rect, mPaint); cachedCanvas.drawBitmap(((BitmapDrawable)getContext().getDrawable(R.drawable.icon_app)).getBitmap(), child1Rect.centerX() - (getContext().getDrawable(R.drawable.icon_app).getBounds().width()/2), child1Rect.centerY() - (getContext().getDrawable(R.drawable.icon_app).getBounds().height()/2), null); canvas.drawBitmap(cachedBitmap, 0, 0, null); } } } 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); } // 获取当前两个子View的Bitmap截图,用于在拖动过程中的实时绘制 private void getBitmapFromChildView() { // 先回收已有的缓存图片 for (Bitmap bitmap: cachedBitmapArray) { if (bitmap != null) { bitmap.recycle(); } } if (mChild0!=null) { cachedBitmapArray[0] = loadBitmapFromViewBySystem(mChild0); } if (mChild1!=null) { cachedBitmapArray[1] = loadBitmapFromViewBySystem(mChild1); } } /** *此方法直接截取屏幕指定view区域的内容 * @param view 需要截取屏幕的图片view * @return Bitmap */ public static Bitmap loadBitmapFromViewBySystem(View view) { if (view == null) { return null; } view.setDrawingCacheEnabled(true); view.buildDrawingCache(); Bitmap bitmap = view.getDrawingCache(); return bitmap; } // 设置拖动时前景图片的颜色值 public void setDragForgroundColor(int dragForgroundColor) { this.dragForgroundColor = dragForgroundColor; mPaint.setColor(dragForgroundColor); } public int getDragForgroundColor() { return dragForgroundColor; } }