diff --git a/build.gradle b/build.gradle
index 43c0708..bd0b3eb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,14 +1,15 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
-
+ ext.kotlin_version = '1.3.70'
+
repositories {
google()
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.1.3'
-
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath 'com.android.tools.build:gradle:3.6.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/gradle.properties b/gradle.properties
index 9fa0cd3..36924eb 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -16,6 +16,14 @@ org.gradle.jvmargs=-Xmx1536m
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+
GROUP=com.mobidevelop.spl
VERSION_NAME=1.0.0
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0664622..a830f45 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Jul 31 20:52:57 MST 2018
+#Tue Mar 03 18:55:52 CET 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
diff --git a/split-pane-layout-demo/build.gradle b/split-pane-layout-demo/build.gradle
index 3e4aa9d..4fc2c26 100644
--- a/split-pane-layout-demo/build.gradle
+++ b/split-pane-layout-demo/build.gradle
@@ -1,15 +1,18 @@
apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-kapt'
android {
- compileSdkVersion 26
+ compileSdkVersion 29
defaultConfig {
applicationId "com.mobidevelop.spl.demo"
minSdkVersion 16
- targetSdkVersion 26
+ targetSdkVersion 29
versionCode 1
versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -22,5 +25,5 @@ android {
dependencies {
implementation project(':split-pane-layout')
- implementation 'com.android.support:appcompat-v7:26.1.0'
+ implementation 'androidx.appcompat:appcompat:1.1.0'
}
diff --git a/split-pane-layout-demo/src/main/java/com/mobidevelop/spl/demo/MainActivity.java b/split-pane-layout-demo/src/main/java/com/mobidevelop/spl/demo/MainActivity.java
deleted file mode 100644
index 623084e..0000000
--- a/split-pane-layout-demo/src/main/java/com/mobidevelop/spl/demo/MainActivity.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.mobidevelop.spl.demo;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.TextView;
-
-import com.mobidevelop.spl.widget.SplitPaneLayout;
-
-import java.text.DecimalFormat;
-import java.text.NumberFormat;
-import java.util.Locale;
-
-public class MainActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- final TextView first = findViewById(R.id.first);
- final TextView second = findViewById(R.id.second);
-
- final SplitPaneLayout layout = findViewById(R.id.layout);
- layout.setOnSplitterPositionChangedListener(new SplitPaneLayout.OnSplitterPositionChangedListener() {
- @Override
- public void onSplitterPositionChanged(SplitPaneLayout splitPaneLayout, boolean fromUser) {
- NumberFormat percent = DecimalFormat.getPercentInstance(Locale.getDefault());
-
- first.setText(percent.format(layout.getSplitterPositionPercent()));
- second.setText(percent.format(1f - layout.getSplitterPositionPercent()));
- }
- });
- layout.post(new Runnable() {
- @Override
- public void run() {
- NumberFormat percent = DecimalFormat.getPercentInstance(Locale.getDefault());
-
- first.setText(percent.format(layout.getSplitterPositionPercent()));
- second.setText(percent.format(1f - layout.getSplitterPositionPercent()));
-
- }
- });
-
- }
-
-}
diff --git a/split-pane-layout-demo/src/main/java/com/mobidevelop/spl/demo/MainActivity.kt b/split-pane-layout-demo/src/main/java/com/mobidevelop/spl/demo/MainActivity.kt
new file mode 100644
index 0000000..e244e43
--- /dev/null
+++ b/split-pane-layout-demo/src/main/java/com/mobidevelop/spl/demo/MainActivity.kt
@@ -0,0 +1,32 @@
+package com.mobidevelop.spl.demo
+
+import android.app.Activity
+import android.os.Bundle
+import com.mobidevelop.spl.widget.SplitPaneLayout
+import com.mobidevelop.spl.widget.SplitPaneLayout.OnSplitterPositionChangedListener
+import kotlinx.android.synthetic.main.activity_main.*
+import java.text.DecimalFormat
+import java.util.*
+
+class MainActivity : Activity(), OnSplitterPositionChangedListener {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+ layout.onSplitterPositionChangedListener = this
+ }
+
+ override fun onResume() {
+ super.onResume()
+ updateViews()
+ }
+
+ override fun onSplitterPositionChanged(splitPaneLayout: SplitPaneLayout, fromUser: Boolean) {
+ updateViews()
+ }
+
+ private fun updateViews() {
+ val percent = DecimalFormat.getPercentInstance(Locale.getDefault())
+ first.text = percent.format(layout.splitterPositionPercent.toDouble())
+ second.text = percent.format(1f - layout.splitterPositionPercent.toDouble())
+ }
+}
\ No newline at end of file
diff --git a/split-pane-layout-demo/src/main/res/layout-land/activity_main.xml b/split-pane-layout-demo/src/main/res/layout-land/activity_main.xml
index 2156e45..ed7c720 100644
--- a/split-pane-layout-demo/src/main/res/layout-land/activity_main.xml
+++ b/split-pane-layout-demo/src/main/res/layout-land/activity_main.xml
@@ -1,28 +1,31 @@
-
-
+ spl:splitterPosition="33%"
+ spl:splitterSize="4dip"
+ spl:splitterTouchSlop="48dp">
\ No newline at end of file
diff --git a/split-pane-layout-demo/src/main/res/layout/activity_main.xml b/split-pane-layout-demo/src/main/res/layout/activity_main.xml
index be908c8..61ebc4b 100644
--- a/split-pane-layout-demo/src/main/res/layout/activity_main.xml
+++ b/split-pane-layout-demo/src/main/res/layout/activity_main.xml
@@ -1,28 +1,31 @@
-
-
+ spl:splitterPosition="50%"
+ spl:splitterSize="4dip"
+ spl:splitterTouchSlop="48dp">
\ No newline at end of file
diff --git a/split-pane-layout/build.gradle b/split-pane-layout/build.gradle
index 574f80b..d09cab5 100644
--- a/split-pane-layout/build.gradle
+++ b/split-pane-layout/build.gradle
@@ -1,15 +1,18 @@
apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-kapt'
android {
- compileSdkVersion 26
+ compileSdkVersion 29
defaultConfig {
minSdkVersion 16
- targetSdkVersion 26
+ targetSdkVersion 29
versionCode 1
versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
diff --git a/split-pane-layout/src/main/java/com/mobidevelop/spl/widget/SplitPaneLayout.java b/split-pane-layout/src/main/java/com/mobidevelop/spl/widget/SplitPaneLayout.java
deleted file mode 100644
index 85c0cc1..0000000
--- a/split-pane-layout/src/main/java/com/mobidevelop/spl/widget/SplitPaneLayout.java
+++ /dev/null
@@ -1,736 +0,0 @@
-/*
- * Android Split Pane Layout.
- * https://github.com/MobiDevelop/android-split-pane-layout
- *
- * Copyright (C) 2012 Justin Shapcott
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.mobidevelop.spl.widget;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.PaintDrawable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.HapticFeedbackConstants;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-
-import com.mobidevelop.spl.R;
-
-/**
- * A layout that splits the available space between two child views.
- *
- * An optionally movable bar exists between the children which allows the user
- * to redistribute the space allocated to each view.
- */
-public class SplitPaneLayout extends ViewGroup {
-
- public interface OnSplitterPositionChangedListener {
- void onSplitterPositionChanged(SplitPaneLayout splitPaneLayout, boolean fromUser);
- }
-
- public static final int ORIENTATION_HORIZONTAL = 0;
- public static final int ORIENTATION_VERTICAL = 1;
-
- private int mOrientation = 0;
- private int mSplitterSize = 8;
- private boolean mSplitterMovable = true;
- private int mSplitterPosition = Integer.MIN_VALUE;
- private float mSplitterPositionPercent = 0.5f;
- private int mSplitterTouchSlop = 0;
-
- private int mPaneSizeMin = 0;
-
- private Drawable mSplitterDrawable;
- private Drawable mSplitterDraggingDrawable;
-
- private Rect mSplitterBounds = new Rect();
- private Rect mSplitterTouchBounds = new Rect();
- private Rect mSplitterDraggingBounds = new Rect();
-
- private OnSplitterPositionChangedListener mOnSplitterPositionChangedListener;
-
- private int lastTouchX;
- private int lastTouchY;
-
- private boolean isDragging = false;
- private boolean isMovingSplitter = false;
-
- private boolean isMeasured = false;
-
- public SplitPaneLayout(Context context) {
- super(context);
- mSplitterPositionPercent = 0.5f;
- mSplitterDrawable = new PaintDrawable(0x88FFFFFF);
- mSplitterDraggingDrawable = new PaintDrawable(0x88FFFFFF);
- }
-
- public SplitPaneLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- extractAttributes(context, attrs);
- setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
- setFocusable(true);
- setFocusableInTouchMode(false);
- }
-
- public SplitPaneLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- extractAttributes(context, attrs);
- setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
- setFocusable(true);
- setFocusableInTouchMode(false);
- }
-
- private void extractAttributes(Context context, AttributeSet attrs) {
- if (attrs != null) {
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SplitPaneLayout);
- mOrientation = a.getInt(R.styleable.SplitPaneLayout_orientation, 0);
- mSplitterSize = a.getDimensionPixelSize(R.styleable.SplitPaneLayout_splitterSize, context.getResources().getDimensionPixelSize(R.dimen.spl_default_splitter_size));
- mSplitterMovable = a.getBoolean(R.styleable.SplitPaneLayout_splitterMovable, true);
- TypedValue value = a.peekValue(R.styleable.SplitPaneLayout_splitterPosition);
- if (value != null) {
- if (value.type == TypedValue.TYPE_DIMENSION) {
- mSplitterPosition = a.getDimensionPixelSize(R.styleable.SplitPaneLayout_splitterPosition, Integer.MIN_VALUE);
- } else if (value.type == TypedValue.TYPE_FRACTION) {
- mSplitterPositionPercent = a.getFraction(R.styleable.SplitPaneLayout_splitterPosition, 100, 100, 50) * 0.01f;
- }
- } else {
- mSplitterPosition = Integer.MIN_VALUE;
- mSplitterPositionPercent = 0.5f;
- }
-
- value = a.peekValue(R.styleable.SplitPaneLayout_splitterBackground);
- if (value != null) {
- if (value.type == TypedValue.TYPE_REFERENCE ||
- value.type == TypedValue.TYPE_STRING) {
- mSplitterDrawable = a.getDrawable(R.styleable.SplitPaneLayout_splitterBackground);
- } else if (value.type == TypedValue.TYPE_INT_COLOR_ARGB8 ||
- value.type == TypedValue.TYPE_INT_COLOR_ARGB4 ||
- value.type == TypedValue.TYPE_INT_COLOR_RGB8 ||
- value.type == TypedValue.TYPE_INT_COLOR_RGB4) {
- mSplitterDrawable = new PaintDrawable(a.getColor(R.styleable.SplitPaneLayout_splitterBackground, 0xFF000000));
- }
- }
- value = a.peekValue(R.styleable.SplitPaneLayout_splitterDraggingBackground);
- if (value != null) {
- if (value.type == TypedValue.TYPE_REFERENCE ||
- value.type == TypedValue.TYPE_STRING) {
- mSplitterDraggingDrawable = a.getDrawable(R.styleable.SplitPaneLayout_splitterDraggingBackground);
- } else if (value.type == TypedValue.TYPE_INT_COLOR_ARGB8 ||
- value.type == TypedValue.TYPE_INT_COLOR_ARGB4 ||
- value.type == TypedValue.TYPE_INT_COLOR_RGB8 ||
- value.type == TypedValue.TYPE_INT_COLOR_RGB4) {
- mSplitterDraggingDrawable = new PaintDrawable(a.getColor(R.styleable.SplitPaneLayout_splitterDraggingBackground, 0x88FFFFFF));
- }
- } else {
- mSplitterDraggingDrawable = new PaintDrawable(0x88FFFFFF);
- }
- mSplitterTouchSlop = a.getDimensionPixelSize(R.styleable.SplitPaneLayout_splitterTouchSlop, ViewConfiguration.get(context).getScaledTouchSlop());
- mPaneSizeMin = a.getDimensionPixelSize(R.styleable.SplitPaneLayout_paneSizeMin, 0);
- a.recycle();
- }
- }
-
- private void computeSplitterPosition() {
-
- int measuredWidth = getMeasuredWidth();
- int measuredHeight = getMeasuredHeight();
-
- if (measuredWidth > 0 && measuredHeight > 0) {
- switch (mOrientation) {
- case ORIENTATION_HORIZONTAL: {
- if (mSplitterPosition == Integer.MIN_VALUE && mSplitterPositionPercent < 0) {
- mSplitterPosition = measuredWidth / 2;
- } else if (mSplitterPosition == Integer.MIN_VALUE && mSplitterPositionPercent >= 0) {
- mSplitterPosition = (int) (measuredWidth * mSplitterPositionPercent);
- if (!between(mSplitterPosition, getMinSplitterPosition(), getMaxSplitterPosition())) {
- mSplitterPosition = clamp(mSplitterPosition, getMinSplitterPosition(), getMaxSplitterPosition());
- mSplitterPositionPercent = (float) mSplitterPosition / (float) measuredWidth;
- }
- } else if (mSplitterPosition != Integer.MIN_VALUE && mSplitterPositionPercent < 0) {
- if (!between(mSplitterPosition, getMinSplitterPosition(), getMaxSplitterPosition())) {
- mSplitterPosition = clamp(mSplitterPosition, getMinSplitterPosition(), getMaxSplitterPosition());
- }
- mSplitterPositionPercent = (float) mSplitterPosition / (float) measuredWidth;
- }
- mSplitterBounds.set(mSplitterPosition - (mSplitterSize / 2), 0, mSplitterPosition + (mSplitterSize / 2), measuredHeight);
- mSplitterTouchBounds.set(mSplitterBounds.left - mSplitterTouchSlop, mSplitterBounds.top, mSplitterBounds.right + mSplitterTouchSlop, mSplitterBounds.bottom);
- break;
- }
- case ORIENTATION_VERTICAL: {
- if (mSplitterPosition == Integer.MIN_VALUE && mSplitterPositionPercent < 0) {
- mSplitterPosition = measuredHeight / 2;
- } else if (mSplitterPosition == Integer.MIN_VALUE && mSplitterPositionPercent >= 0) {
- mSplitterPosition = (int) (measuredHeight * mSplitterPositionPercent);
- if (!between(mSplitterPosition, getMinSplitterPosition(), getMaxSplitterPosition())) {
- mSplitterPosition = clamp(mSplitterPosition, getMinSplitterPosition(), getMaxSplitterPosition());
- mSplitterPositionPercent = (float) mSplitterPosition / (float) measuredHeight;
- }
- } else if (mSplitterPosition != Integer.MIN_VALUE && mSplitterPositionPercent < 0) {
- if (!between(mSplitterPosition, getMinSplitterPosition(), getMaxSplitterPosition())) {
- mSplitterPosition = clamp(mSplitterPosition, getMinSplitterPosition(), getMaxSplitterPosition());
- }
- mSplitterPositionPercent = (float) mSplitterPosition / (float) measuredHeight;
- }
- mSplitterBounds.set(0, mSplitterPosition - (mSplitterSize / 2), measuredWidth, mSplitterPosition + (mSplitterSize / 2));
- mSplitterTouchBounds.set(mSplitterBounds.left, mSplitterBounds.top - mSplitterTouchSlop / 2, mSplitterBounds.right, mSplitterBounds.bottom + mSplitterTouchSlop / 2);
- break;
- }
- }
- }
-
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- int measuredWidth = getMeasuredWidth();
- int measuredHeight = getMeasuredHeight();
-
- check();
-
- if (measuredWidth > 0 && measuredHeight > 0) {
-
- computeSplitterPosition();
-
- switch (mOrientation) {
- case ORIENTATION_HORIZONTAL: {
- getChildAt(0).measure(MeasureSpec.makeMeasureSpec(mSplitterPosition - (mSplitterSize / 2), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY));
- getChildAt(1).measure(MeasureSpec.makeMeasureSpec(measuredWidth - (mSplitterSize / 2) - mSplitterPosition, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY));
- break;
- }
- case ORIENTATION_VERTICAL: {
- getChildAt(0).measure(MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mSplitterPosition - (mSplitterSize / 2), MeasureSpec.EXACTLY));
- getChildAt(1).measure(MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(measuredHeight - (mSplitterSize / 2) - mSplitterPosition, MeasureSpec.EXACTLY));
- break;
- }
- }
-
- isMeasured = true;
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int w = r - l;
- int h = b - t;
- switch (mOrientation) {
- case ORIENTATION_HORIZONTAL: {
- getChildAt(0).layout(0, 0, mSplitterPosition - (mSplitterSize / 2), h);
- getChildAt(1).layout(mSplitterPosition + (mSplitterSize / 2), 0, r, h);
- break;
- }
- case ORIENTATION_VERTICAL: {
- getChildAt(0).layout(0, 0, w, mSplitterPosition - (mSplitterSize / 2));
- getChildAt(1).layout(0, mSplitterPosition + (mSplitterSize / 2), w, h);
- break;
- }
- }
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- boolean remeasure = false;
- int offset = mSplitterSize;
- if (event.isShiftPressed()) {
- offset *= 5;
- }
- switch (mOrientation) {
- case ORIENTATION_HORIZONTAL:
- if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
- mSplitterPosition = clamp(mSplitterPosition - offset, getMinSplitterPosition(), getMaxSplitterPosition());
- mSplitterPositionPercent = -1;
- remeasure = true;
- } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
- mSplitterPosition = clamp(mSplitterPosition + offset, getMinSplitterPosition(), getMaxSplitterPosition());
- mSplitterPositionPercent = -1;
- remeasure = true;
- }
- break;
- case ORIENTATION_VERTICAL:
- if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
- mSplitterPosition = clamp(mSplitterPosition - offset, getMinSplitterPosition(), getMaxSplitterPosition());
- mSplitterPositionPercent = -1;
- remeasure = true;
- } else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
- mSplitterPosition = clamp(mSplitterPosition + offset, getMinSplitterPosition(), getMaxSplitterPosition());
- mSplitterPositionPercent = -1;
- remeasure = true;
- }
- break;
- }
- if (remeasure) {
- remeasure();
- notifySplitterPositionChanged(true);
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (mSplitterMovable) {
- int x = (int) event.getX();
- int y = (int) event.getY();
-
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- handleTouchDown(x, y);
- break;
- case MotionEvent.ACTION_MOVE:
- handleTouchMove(x, y);
- break;
- case MotionEvent.ACTION_UP:
- handleTouchUp(x, y);
- break;
- }
- return true;
- }
- return false;
- }
-
- private void handleTouchDown(int x, int y) {
- if (mSplitterTouchBounds.contains(x, y)) {
- performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
- isDragging = true;
- mSplitterDraggingBounds.set(mSplitterBounds);
- invalidate(mSplitterDraggingBounds);
- lastTouchX = x;
- lastTouchY = y;
- }
- }
-
- private void handleTouchMove(int x, int y) {
- if (isDragging) {
- if (!isMovingSplitter) {
- // Verify we've moved far enough to leave the touch bounds before moving the splitter
- if (mSplitterTouchBounds.contains(x, y)) {
- return;
- } else {
- isMovingSplitter = true;
- }
- }
- boolean take = true;
- switch (mOrientation) {
- case ORIENTATION_HORIZONTAL: {
- mSplitterDraggingBounds.offset(x - lastTouchX, 0);
- if (mSplitterDraggingBounds.centerX() < getMinSplitterPosition()) {
- take = false;
- mSplitterDraggingBounds.offset(getMinSplitterPosition() - mSplitterDraggingBounds.centerX(), 0);
- }
- if (mSplitterDraggingBounds.centerX() > getMaxSplitterPosition()) {
- take = false;
- mSplitterDraggingBounds.offset(getMaxSplitterPosition() - mSplitterDraggingBounds.centerX(), 0);
- }
- break;
- }
- case ORIENTATION_VERTICAL: {
- mSplitterDraggingBounds.offset(0, y - lastTouchY);
- if (mSplitterDraggingBounds.centerY() < getMinSplitterPosition()) {
- take = false;
- mSplitterDraggingBounds.offset(0, getMinSplitterPosition() - mSplitterDraggingBounds.centerY());
- }
- if (mSplitterDraggingBounds.centerY() > getMaxSplitterPosition()) {
- take = false;
- mSplitterDraggingBounds.offset(0, getMaxSplitterPosition() - mSplitterDraggingBounds.centerY());
- }
- break;
- }
- }
- if (take) {
- lastTouchX = x;
- lastTouchY = y;
- }
- invalidate();
- }
- }
-
- private void handleTouchUp(int x, int y) {
- if (isDragging) {
- isDragging = false;
- isMovingSplitter = false;
- switch (mOrientation) {
- case ORIENTATION_HORIZONTAL: {
- mSplitterPosition = clamp(x, getMinSplitterPosition(), getMaxSplitterPosition());
- mSplitterPositionPercent = -1;
- break;
- }
- case ORIENTATION_VERTICAL: {
- mSplitterPosition = clamp(y, getMinSplitterPosition(), getMaxSplitterPosition());
- mSplitterPositionPercent = -1;
- break;
- }
- }
- remeasure();
- notifySplitterPositionChanged(true);
- }
- }
-
- private int getMinSplitterPosition() {
- return mPaneSizeMin;
- }
-
- private int getMaxSplitterPosition() {
- switch (mOrientation) {
- case ORIENTATION_HORIZONTAL:
- return getMeasuredWidth() - mPaneSizeMin;
- case ORIENTATION_VERTICAL:
- return getMeasuredHeight() - mPaneSizeMin;
- }
- return 0;
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- Parcelable superState = super.onSaveInstanceState();
- SavedState ss = new SavedState(superState);
- ss.mSplitterPositionPercent = mSplitterPositionPercent;
- return ss;
- }
-
- @Override
- public void onRestoreInstanceState(Parcelable state) {
- if (!(state instanceof SavedState)) {
- super.onRestoreInstanceState(state);
- return;
- }
- SavedState ss = (SavedState) state;
- super.onRestoreInstanceState(ss.getSuperState());
- setSplitterPositionPercent(ss.mSplitterPositionPercent);
- }
-
- /**
- * Convenience for calling own measure method.
- */
- private void remeasure() {
- // TODO: Performance: Guard against calling too often, can it be done without requestLayout?
- forceLayout();
- measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)
- );
- requestLayout();
- }
-
- /**
- * Checks that we have exactly two children.
- */
- private void check() {
- if (getChildCount() != 2) {
- throw new RuntimeException("SplitPaneLayout must have exactly two child views.");
- }
- }
-
- private void enforcePaneSizeMin() {
-
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
- if (mSplitterDrawable != null) {
- mSplitterDrawable.setState(getDrawableState());
- mSplitterDrawable.setBounds(mSplitterBounds);
- mSplitterDrawable.draw(canvas);
- }
- if (isDragging) {
- mSplitterDraggingDrawable.setState(getDrawableState());
- mSplitterDraggingDrawable.setBounds(mSplitterDraggingBounds);
- mSplitterDraggingDrawable.draw(canvas);
- }
- }
-
- /**
- * Gets the current drawable used for the splitter.
- *
- * @return the drawable used for the splitter
- */
- public Drawable getSplitterDrawable() {
- return mSplitterDrawable;
- }
-
- /**
- * Sets the drawable used for the splitter.
- *
- * @param splitterDrawable the desired orientation of the layout
- */
- public void setSplitterDrawable(Drawable splitterDrawable) {
- mSplitterDrawable = splitterDrawable;
- if (getChildCount() == 2) {
- remeasure();
- }
- }
-
- /**
- * Gets the current drawable used for the splitter dragging overlay.
- *
- * @return the drawable used for the splitter
- */
- public Drawable getSplitterDraggingDrawable() {
- return mSplitterDraggingDrawable;
- }
-
- /**
- * Sets the drawable used for the splitter dragging overlay.
- *
- * @param splitterDraggingDrawable the drawable to use while dragging the splitter
- */
- public void setSplitterDraggingDrawable(Drawable splitterDraggingDrawable) {
- mSplitterDraggingDrawable = splitterDraggingDrawable;
- if (isDragging) {
- invalidate();
- }
- }
-
- /**
- * Gets the current orientation of the layout.
- *
- * @return the orientation of the layout
- */
- public int getOrientation() {
- return mOrientation;
- }
-
- /**
- * Sets the orientation of the layout.
- *
- * @param orientation the desired orientation of the layout
- */
- public void setOrientation(int orientation) {
- if (mOrientation != orientation) {
- mOrientation = orientation;
- if (getChildCount() == 2) {
- remeasure();
- }
- }
- }
-
- /**
- * Gets the current size of the splitter in pixels.
- *
- * @return the size of the splitter
- */
- public int getSplitterSize() {
- return mSplitterSize;
- }
-
- /**
- * Sets the current size of the splitter in pixels.
- *
- * @param splitterSize the desired size of the splitter
- */
- public void setSplitterSize(int splitterSize) {
- mSplitterSize = splitterSize;
- if (getChildCount() == 2) {
- remeasure();
- }
- }
-
- /**
- * Gets whether the splitter is movable by the user.
- *
- * @return whether the splitter is movable
- */
- public boolean isSplitterMovable() {
- return mSplitterMovable;
- }
-
- /**
- * Sets whether the splitter is movable by the user.
- *
- * @param splitterMovable whether the splitter is movable
- */
- public void setSplitterMovable(boolean splitterMovable) {
- mSplitterMovable = splitterMovable;
- }
-
- /**
- * Gets the current position of the splitter in pixels.
- *
- * @return the position of the splitter
- */
- public int getSplitterPosition() {
- return mSplitterPosition;
- }
-
- /**
- * Sets the current position of the splitter in pixels.
- *
- * @param position the desired position of the splitter
- */
- public void setSplitterPosition(int position) {
- mSplitterPosition = clamp(position, 0, Integer.MAX_VALUE);
- mSplitterPositionPercent = -1;
- remeasure();
- notifySplitterPositionChanged(false);
- }
-
- /**
- * Gets the current position of the splitter as a percent.
- *
- * @return the position of the splitter
- */
- public float getSplitterPositionPercent() {
- return mSplitterPositionPercent;
- }
-
- /**
- * Sets the current position of the splitter as a percentage of the layout.
- *
- * @param position the desired position of the splitter
- */
- public void setSplitterPositionPercent(float position) {
- mSplitterPosition = Integer.MIN_VALUE;
- mSplitterPositionPercent = clamp(position, 0, 1);
- remeasure();
- notifySplitterPositionChanged(false);
- }
-
- /**
- * Gets the current "touch slop" which is used to extends the grab size of the splitter
- * and requires the splitter to be dragged at least this far to be considered a move.
- *
- * @return the current "touch slop" of the splitter
- */
- public int getSplitterTouchSlop() {
- return mSplitterTouchSlop;
- }
-
- /**
- * Sets the current "touch slop" which is used to extends the grab size of the splitter
- * and requires the splitter to be dragged at least this far to be considered a move.
- *
- * @param splitterTouchSlop the desired "touch slop" of the splitter
- */
- public void setSplitterTouchSlop(int splitterTouchSlop) {
- this.mSplitterTouchSlop = splitterTouchSlop;
- computeSplitterPosition();
- }
-
- /**
- * Gets the minimum size of panes, in pixels.
- *
- * @return the minimum size of panes, in pixels.
- */
- public int getPaneSizeMin() {
- return mPaneSizeMin;
- }
-
- /**
- * Sets the minimum size of panes, in pixels.
- *
- * @param paneSizeMin the minimum size of panes, in pixels
- */
- public void setPaneSizeMin(int paneSizeMin) {
- mPaneSizeMin = paneSizeMin;
- if (isMeasured) {
- int newSplitterPosition = clamp(mSplitterPosition, getMinSplitterPosition(), getMaxSplitterPosition());
- if(newSplitterPosition != mSplitterPosition) {
- setSplitterPosition(newSplitterPosition);
- }
- }
- }
-
- /**
- * Gets the OnSplitterPositionChangedListener to receive callbacks when the splitter position is changed
- *
- * @return the OnSplitterPositionChangedListener to receive callbacks when the splitter position is changed
- */
- public OnSplitterPositionChangedListener getOnSplitterPositionChangedListener() {
- return mOnSplitterPositionChangedListener;
- }
-
- /**
- * Sets the OnSplitterPositionChangedListener to receive callbacks when the splitter position is changed
- *
- * @param l the OnSplitterPositionChangedListener to receive callbacks when the splitter position is changed
- */
- public void setOnSplitterPositionChangedListener(OnSplitterPositionChangedListener l) {
- this.mOnSplitterPositionChangedListener = l;
- }
-
- private void notifySplitterPositionChanged(boolean fromUser) {
- if (mOnSplitterPositionChangedListener != null) {
- Log.d("SPL", "Splitter Position Changed");
- mOnSplitterPositionChangedListener.onSplitterPositionChanged(this, fromUser);
- }
- }
-
- private static float clamp(float value, float min, float max) {
- if (value < min) {
- return min;
- } else if (value > max) {
- return max;
- }
- return value;
- }
-
- private static int clamp(int value, int min, int max) {
- if (value < min) {
- return min;
- } else if (value > max) {
- return max;
- }
- return value;
- }
-
- private static boolean between(int value, int min, int max) {
- return min <= value && value <= max;
- }
-
- /**
- * Holds important values when we need to save instance state.
- */
- public static class SavedState extends BaseSavedState {
- public static final Creator CREATOR = new Creator() {
-
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
-
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
-
- };
-
- float mSplitterPositionPercent;
-
- SavedState(Parcelable superState) {
- super(superState);
- }
-
- private SavedState(Parcel in) {
- super(in);
- mSplitterPositionPercent = in.readFloat();
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- super.writeToParcel(out, flags);
- out.writeFloat(mSplitterPositionPercent);
- }
- }
-
-}
\ No newline at end of file
diff --git a/split-pane-layout/src/main/java/com/mobidevelop/spl/widget/SplitPaneLayout.kt b/split-pane-layout/src/main/java/com/mobidevelop/spl/widget/SplitPaneLayout.kt
new file mode 100644
index 0000000..8580c67
--- /dev/null
+++ b/split-pane-layout/src/main/java/com/mobidevelop/spl/widget/SplitPaneLayout.kt
@@ -0,0 +1,597 @@
+/*
+ * Android Split Pane Layout.
+ * https://github.com/MobiDevelop/android-split-pane-layout
+ *
+ * Copyright (C) 2012 Justin Shapcott
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.mobidevelop.spl.widget
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.PaintDrawable
+import android.os.Parcel
+import android.os.Parcelable
+import android.util.AttributeSet
+import android.util.Log
+import android.util.TypedValue
+import android.view.*
+import com.mobidevelop.spl.R
+
+/**
+ * A layout that splits the available space between two child views.
+ *
+ *
+ * An optionally movable bar exists between the children which allows the user
+ * to redistribute the space allocated to each view.
+ */
+class SplitPaneLayout : ViewGroup {
+ interface OnSplitterPositionChangedListener {
+ fun onSplitterPositionChanged(splitPaneLayout: SplitPaneLayout, fromUser: Boolean)
+ }
+
+ private var mOrientation = DEFAULT_ORIENTATION
+ private var mSplitterSize = 8
+ private var mSplitterPosition = Int.MIN_VALUE
+ private var mSplitterPositionPercent = DEFAULT_POSITION_PERCENT
+ private var mSplitterTouchSlop = 0
+ private var minSplitterPosition = 0
+ get() {
+ return if (field < paneSizeMin) paneSizeMin else field
+ }
+ private var mSplitterDrawable: Drawable = DEFAULT_DRAWABLE
+ private var mSplitterDraggingDrawable: Drawable = DEFAULT_DRAWABLE
+ private val mSplitterBounds = Rect()
+ private val mSplitterTouchBounds = Rect()
+ private val mSplitterDraggingBounds = Rect()
+ private var lastTouchX = 0
+ private var lastTouchY = 0
+ private var isDragging = false
+ private var isMovingSplitter = false
+ private var isMeasured = false
+
+ constructor(context: Context) : super(context) {
+ init(null)
+ }
+
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
+ init(attrs)
+ }
+
+ constructor(context: Context, attrs: AttributeSet?, defStyle: Int) :
+ super(context, attrs, defStyle) {
+ init(attrs)
+ }
+
+ private fun init(attrs: AttributeSet?) {
+ extractAttributes(context, attrs)
+ descendantFocusability = FOCUS_AFTER_DESCENDANTS
+ isFocusable = true
+ isFocusableInTouchMode = false
+ }
+
+ private fun extractAttributes(context: Context, attrs: AttributeSet?) {
+ if (attrs == null) return
+
+ val a = context.obtainStyledAttributes(attrs, R.styleable.SplitPaneLayout)
+ mOrientation = a.getInt(R.styleable.SplitPaneLayout_orientation, ORIENTATION_HORIZONTAL)
+ mSplitterSize = a.getDimensionPixelSize(
+ R.styleable.SplitPaneLayout_splitterSize,
+ context.resources.getDimensionPixelSize(R.dimen.spl_default_splitter_size)
+ )
+ isSplitterMovable = a.getBoolean(R.styleable.SplitPaneLayout_splitterMovable, true)
+ a.peekValue(R.styleable.SplitPaneLayout_splitterPosition)?.let {
+ when (it.type) {
+ TypedValue.TYPE_DIMENSION -> {
+ mSplitterPosition = a.getDimensionPixelSize(R.styleable.SplitPaneLayout_splitterPosition, Int.MIN_VALUE)
+ }
+ TypedValue.TYPE_FRACTION -> {
+ val fraction = a.getFraction(R.styleable.SplitPaneLayout_splitterPosition, 100, 100, 50f)
+ mSplitterPositionPercent = fraction * 0.01f
+ }
+ }
+ }
+ mSplitterDrawable = a.peekValue(R.styleable.SplitPaneLayout_splitterBackground)?.let {
+ when (it.type) {
+ TypedValue.TYPE_REFERENCE,
+ TypedValue.TYPE_STRING ->
+ a.getDrawable(R.styleable.SplitPaneLayout_splitterBackground)
+ TypedValue.TYPE_INT_COLOR_ARGB8,
+ TypedValue.TYPE_INT_COLOR_ARGB4,
+ TypedValue.TYPE_INT_COLOR_RGB8,
+ TypedValue.TYPE_INT_COLOR_RGB4 -> {
+ val backgroundColor = a.getColor(R.styleable.SplitPaneLayout_splitterBackground, DEFAULT_SPLITTER_COLOR)
+ PaintDrawable(backgroundColor)
+ }
+ else -> DEFAULT_DRAWABLE
+ }
+ } ?: DEFAULT_DRAWABLE
+ mSplitterDraggingDrawable = a.peekValue(R.styleable.SplitPaneLayout_splitterDraggingBackground)?.let {
+ when (it.type) {
+ TypedValue.TYPE_REFERENCE,
+ TypedValue.TYPE_STRING ->
+ a.getDrawable(R.styleable.SplitPaneLayout_splitterDraggingBackground)
+ TypedValue.TYPE_INT_COLOR_ARGB8,
+ TypedValue.TYPE_INT_COLOR_ARGB4,
+ TypedValue.TYPE_INT_COLOR_RGB8,
+ TypedValue.TYPE_INT_COLOR_RGB4 -> PaintDrawable(
+ a.getColor(
+ R.styleable.SplitPaneLayout_splitterDraggingBackground,
+ DEFAULT_DRAGGING_COLOR
+ )
+ )
+ else -> DEFAULT_DRAWABLE
+ }
+ } ?: DEFAULT_DRAWABLE
+ mSplitterTouchSlop = a.getDimensionPixelSize(
+ R.styleable.SplitPaneLayout_splitterTouchSlop,
+ ViewConfiguration.get(context).scaledTouchSlop
+ )
+ minSplitterPosition = a.getDimensionPixelSize(R.styleable.SplitPaneLayout_paneSizeMin, 0)
+ a.recycle()
+ }
+
+ private fun computeSplitterPosition() {
+ if (measuredWidth <= 0 || measuredHeight <= 0) return
+
+ val measuredSize =
+ if (mOrientation == ORIENTATION_HORIZONTAL) measuredWidth else measuredHeight
+
+ when {
+ mSplitterPosition == Int.MIN_VALUE && mSplitterPositionPercent < 0 ->
+ mSplitterPosition = measuredSize / 2
+ mSplitterPosition == Int.MIN_VALUE && mSplitterPositionPercent >= 0 -> {
+ mSplitterPosition = (measuredSize * mSplitterPositionPercent).toInt()
+ if (!mSplitterPosition.between(minSplitterPosition, maxSplitterPosition)
+ ) {
+ mSplitterPosition = clamp(mSplitterPosition, minSplitterPosition, maxSplitterPosition)
+ mSplitterPositionPercent = relativePositionPercent(measuredSize)
+ }
+ }
+ mSplitterPosition != Int.MIN_VALUE && mSplitterPositionPercent < 0 -> {
+ if (!mSplitterPosition.between(minSplitterPosition, maxSplitterPosition)
+ ) {
+ mSplitterPosition = clamp(mSplitterPosition, minSplitterPosition, maxSplitterPosition)
+ }
+ mSplitterPositionPercent = relativePositionPercent(measuredSize)
+ }
+ }
+
+ when (mOrientation) {
+ ORIENTATION_HORIZONTAL -> {
+ mSplitterBounds.set(mSplitterPosition - mSplitterSize / 2, 0, mSplitterPosition + mSplitterSize / 2, measuredHeight)
+ mSplitterTouchBounds.set(mSplitterBounds.left - mSplitterTouchSlop, mSplitterBounds.top, mSplitterBounds.right + mSplitterTouchSlop, mSplitterBounds.bottom)
+ }
+ ORIENTATION_VERTICAL -> {
+ mSplitterBounds.set(0, mSplitterPosition - mSplitterSize / 2, measuredWidth, mSplitterPosition + mSplitterSize / 2)
+ mSplitterTouchBounds.set(mSplitterBounds.left, mSplitterBounds.top - mSplitterTouchSlop / 2, mSplitterBounds.right, mSplitterBounds.bottom + mSplitterTouchSlop / 2)
+ }
+ }
+ }
+
+ private fun relativePositionPercent(measuredSize: Int): Float = when {
+ mSplitterPosition <= mSplitterSize / 2 -> 0.0f
+ mSplitterPosition >= measuredSize - mSplitterSize / 2 -> 1.0f
+ else -> mSplitterPosition / measuredSize.toFloat()
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ check()
+ if (measuredWidth <= 0 || measuredHeight <= 0) return
+
+ mSplitterPosition = Int.MIN_VALUE
+ measure()
+ }
+
+ private fun measure() {
+ if (measuredWidth <= 0 || measuredHeight <= 0) return
+ computeSplitterPosition()
+ when (mOrientation) {
+ ORIENTATION_HORIZONTAL -> {
+ getChildAt(0).measure(
+ MeasureSpec.makeMeasureSpec(mSplitterPosition - mSplitterSize / 2, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)
+ )
+ getChildAt(1).measure(
+ MeasureSpec.makeMeasureSpec(measuredWidth - mSplitterSize / 2 - mSplitterPosition, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)
+ )
+ }
+ ORIENTATION_VERTICAL -> {
+ getChildAt(0).measure(
+ MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(mSplitterPosition - mSplitterSize / 2, MeasureSpec.EXACTLY)
+ )
+ getChildAt(1).measure(
+ MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(measuredHeight - mSplitterSize / 2 - mSplitterPosition, MeasureSpec.EXACTLY)
+ )
+ }
+ }
+ isMeasured = true
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ val width = right - left
+ val height = bottom - top
+ when (mOrientation) {
+ ORIENTATION_HORIZONTAL -> {
+ getChildAt(0).layout(0, 0, mSplitterPosition - mSplitterSize / 2, height)
+ getChildAt(1).layout(mSplitterPosition + mSplitterSize / 2, 0, right, height)
+ }
+ ORIENTATION_VERTICAL -> {
+ getChildAt(0).layout(0, 0, width, mSplitterPosition - mSplitterSize / 2)
+ getChildAt(1).layout(0, mSplitterPosition + mSplitterSize / 2, width, height)
+ }
+ }
+ }
+
+ override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
+ var remeasure = false
+ var offset = mSplitterSize
+ if (event.isShiftPressed) {
+ offset *= 5
+ }
+ when {
+ (mOrientation == ORIENTATION_HORIZONTAL && keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ||
+ (mOrientation == ORIENTATION_VERTICAL && keyCode == KeyEvent.KEYCODE_DPAD_UP) -> {
+ mSplitterPosition = clamp(mSplitterPosition - offset, minSplitterPosition, maxSplitterPosition)
+ mSplitterPositionPercent = -1f
+ remeasure = true
+ }
+
+ (mOrientation == ORIENTATION_HORIZONTAL && keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) ||
+ (mOrientation == ORIENTATION_VERTICAL && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) -> {
+ mSplitterPosition = clamp(mSplitterPosition + offset, minSplitterPosition, maxSplitterPosition)
+ mSplitterPositionPercent = -1f
+ remeasure = true
+ }
+ }
+ if (remeasure) {
+ remeasure()
+ notifySplitterPositionChanged(true)
+ return true
+ }
+ return super.onKeyDown(keyCode, event)
+ }
+
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
+ if (isSplitterMovable) {
+ val x = event.x.toInt()
+ val y = event.y.toInt()
+ return if (event.action == MotionEvent.ACTION_DOWN)
+ (mSplitterTouchBounds.contains(x, y))
+ else false
+ }
+ return false
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ if (isSplitterMovable) {
+ val x = event.x.toInt()
+ val y = event.y.toInt()
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> handleTouchDown(x, y)
+ MotionEvent.ACTION_MOVE -> handleTouchMove(x, y)
+ MotionEvent.ACTION_UP -> handleTouchUp(x, y)
+ }
+ return true
+ }
+ return false
+ }
+
+ private fun handleTouchDown(x: Int, y: Int) {
+ if (mSplitterTouchBounds.contains(x, y)) {
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
+ isDragging = true
+ mSplitterDraggingBounds.set(mSplitterBounds)
+ invalidate(mSplitterDraggingBounds)
+ lastTouchX = x
+ lastTouchY = y
+ }
+ }
+
+ private fun handleTouchMove(x: Int, y: Int) {
+ if (isDragging) {
+ if (!isMovingSplitter) {
+ // Verify we've moved far enough to leave the touch bounds before moving the splitter
+ isMovingSplitter = if (mSplitterTouchBounds.contains(x, y)) {
+ return
+ } else {
+ true
+ }
+ }
+ var take = true
+ when (mOrientation) {
+ ORIENTATION_HORIZONTAL -> {
+ //regular X axis dragging
+ mSplitterDraggingBounds.offset(x - lastTouchX, 0)
+ //clip dragging splitter to left bound
+ if (mSplitterDraggingBounds.centerX() < minSplitterPosition) {
+ take = false
+ mSplitterDraggingBounds.offset(minSplitterPosition - mSplitterDraggingBounds.centerX(), 0)
+ }
+ //clip dragging splitter to right bound
+ if (mSplitterDraggingBounds.centerX() > maxSplitterPosition) {
+ take = false
+ mSplitterDraggingBounds.offset(maxSplitterPosition - mSplitterDraggingBounds.centerX(), 0)
+ }
+ }
+ ORIENTATION_VERTICAL -> {
+ //regular Y axis dragging
+ mSplitterDraggingBounds.offset(0, y - lastTouchY)
+ //clip dragging splitter to top bound
+ if (mSplitterDraggingBounds.centerY() < minSplitterPosition) {
+ take = false
+ mSplitterDraggingBounds.offset(0, minSplitterPosition - mSplitterDraggingBounds.centerY())
+ }
+ //clip dragging splitter to bottom bound
+ if (mSplitterDraggingBounds.centerY() > maxSplitterPosition) {
+ take = false
+ mSplitterDraggingBounds.offset(0, maxSplitterPosition - mSplitterDraggingBounds.centerY())
+ }
+ }
+ }
+ if (take) {
+ lastTouchX = x
+ lastTouchY = y
+ }
+ invalidate()
+ }
+ }
+
+ private fun handleTouchUp(x: Int, y: Int) {
+ if (isDragging) {
+ isDragging = false
+ isMovingSplitter = false
+ val axis = when (mOrientation) {
+ ORIENTATION_HORIZONTAL -> x
+ //ORIENTATION_VERTICAL
+ else -> y
+ }
+ mSplitterPosition = clamp(axis, minSplitterPosition, maxSplitterPosition)
+ mSplitterPositionPercent = -1f
+ remeasure()
+ notifySplitterPositionChanged(true)
+ }
+ }
+
+ private val maxSplitterPosition: Int
+ get() {
+ when (mOrientation) {
+ ORIENTATION_HORIZONTAL -> return measuredWidth - minSplitterPosition
+ ORIENTATION_VERTICAL -> return measuredHeight - minSplitterPosition
+ }
+ return 0
+ }
+
+ public override fun onSaveInstanceState(): Parcelable? {
+ val superState = super.onSaveInstanceState()
+ return SavedState(superState).apply {
+ mSplitterPositionPercent = this@SplitPaneLayout.mSplitterPositionPercent
+ }
+ }
+
+ public override fun onRestoreInstanceState(state: Parcelable) {
+ if (state !is SavedState) {
+ super.onRestoreInstanceState(state)
+ return
+ } else {
+ super.onRestoreInstanceState(state.superState)
+ splitterPositionPercent = state.mSplitterPositionPercent
+ }
+ }
+
+ /**
+ * Convenience for calling own measure method.
+ */
+ private fun remeasure() {
+ // TODO: Performance: Guard against calling too often, can it be done without requestLayout?
+ forceLayout()
+ measure()
+ requestLayout()
+ }
+
+ /**
+ * Checks that layout has exactly two children.
+ */
+ private fun check() {
+ require(childCount == 2) { "SplitPaneLayout must have exactly two child views." }
+ }
+
+ override fun dispatchDraw(canvas: Canvas) {
+ super.dispatchDraw(canvas)
+ mSplitterDrawable.state = drawableState
+ mSplitterDrawable.bounds = mSplitterBounds
+ mSplitterDrawable.draw(canvas)
+ if (isDragging) {
+ mSplitterDraggingDrawable.state = drawableState
+ mSplitterDraggingDrawable.bounds = mSplitterDraggingBounds
+ mSplitterDraggingDrawable.draw(canvas)
+ }
+ }
+
+ /**
+ * Whether the splitter is movable by the user
+ */
+ var isSplitterMovable = DEFAULT_IS_MOVABLE
+
+ /**
+ * Listener to receive callbacks when the splitter position is changed
+ */
+ var onSplitterPositionChangedListener: OnSplitterPositionChangedListener? = null
+
+ /**
+ * Current drawable used for the splitter.
+ */
+ var splitterDrawable: Drawable
+ get() = mSplitterDrawable
+ set(value) {
+ mSplitterDrawable = value
+ check()
+ remeasure()
+ }
+
+ /**
+ * The current drawable to use while dragging the splitter.
+ */
+ var splitterDraggingDrawable: Drawable
+ get() = mSplitterDraggingDrawable
+ set(value) {
+ mSplitterDraggingDrawable = value
+ if (isDragging) {
+ invalidate()
+ }
+ }
+
+ /**
+ * The current orientation of the layout.
+ */
+ var orientation: Int
+ get() = mOrientation
+ set(value) {
+ if (mOrientation != value) {
+ mOrientation = value
+ check()
+ remeasure()
+ }
+ }
+
+ /**
+ * The current size of the splitter in pixels.
+ */
+ var splitterSize: Int
+ get() = mSplitterSize
+ set(value) {
+ mSplitterSize = value
+ check()
+ remeasure()
+ }
+
+ /**
+ * The current position of the splitter in pixels.
+ */
+ var splitterPosition: Int
+ get() = mSplitterPosition
+ set(value) {
+ mSplitterPosition = clamp(value, 0, Int.MAX_VALUE)
+ mSplitterPositionPercent = -1f
+ remeasure()
+ notifySplitterPositionChanged(false)
+ }
+
+ /**
+ * The current position of the splitter as a percentage of the layout.
+ */
+ var splitterPositionPercent: Float
+ get() = mSplitterPositionPercent
+ set(value) {
+ mSplitterPosition = Int.MIN_VALUE
+ mSplitterPositionPercent = clamp(value, 0f, 1f)
+ remeasure()
+ notifySplitterPositionChanged(false)
+ }
+
+ /**
+ * The current "touch slop" which is used to extends the grab size of the splitter
+ * and requires the splitter to be dragged at least this far to be considered a move.
+ */
+ var splitterTouchSlop: Int
+ get() = mSplitterTouchSlop
+ set(value) {
+ mSplitterTouchSlop = value
+ computeSplitterPosition()
+ }
+
+ /**
+ * Minimum size of panes, in pixels.
+ */
+ var paneSizeMin: Int = DEFAULT_PANE_SIZE_MIN
+ set(value) {
+ field = value
+ if (isMeasured) {
+ val newSplitterPosition = clamp(mSplitterPosition, minSplitterPosition, maxSplitterPosition)
+ if (newSplitterPosition != mSplitterPosition) {
+ splitterPosition = newSplitterPosition
+ }
+ }
+ }
+
+ private fun notifySplitterPositionChanged(fromUser: Boolean) {
+ Log.d("SPL", "Splitter Position Changed")
+ onSplitterPositionChangedListener?.onSplitterPositionChanged(this, fromUser)
+ }
+
+ /**
+ * Holds important values when we need to save instance state.
+ */
+ class SavedState : BaseSavedState {
+ var mSplitterPositionPercent = DEFAULT_POSITION_PERCENT
+
+ internal constructor(superState: Parcelable?) : super(superState)
+ private constructor(parcel: Parcel) : super(parcel) {
+ mSplitterPositionPercent = parcel.readFloat()
+ }
+
+ override fun writeToParcel(out: Parcel, flags: Int) {
+ super.writeToParcel(out, flags)
+ out.writeFloat(mSplitterPositionPercent)
+ }
+
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): SavedState {
+ return SavedState(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+
+ companion object {
+ const val ORIENTATION_HORIZONTAL = 0
+ const val ORIENTATION_VERTICAL = 1
+ const val DEFAULT_ORIENTATION = ORIENTATION_HORIZONTAL
+ const val DEFAULT_POSITION_PERCENT = 0.5f
+ const val DEFAULT_IS_MOVABLE = true
+ const val DEFAULT_DRAGGING_COLOR = -0x77000001
+ const val DEFAULT_SPLITTER_COLOR = -0x1000000
+ const val DEFAULT_PANE_SIZE_MIN = 0
+ const val DEFAULT_SPLITTER_TOUCH_SLOP = 0
+ val DEFAULT_DRAWABLE = PaintDrawable(DEFAULT_DRAGGING_COLOR)
+ private fun clamp(value: Float, min: Float, max: Float): Float {
+ return when {
+ value < min -> min
+ value > max -> max
+ else -> value
+ }
+ }
+
+ private fun clamp(value: Int, min: Int, max: Int): Int {
+ return when {
+ value < min -> min
+ value > max -> max
+ else -> value
+ }
+ }
+
+ private fun Int.between(min: Int, max: Int): Boolean {
+ return this in min..max
+ }
+ }
+}
\ No newline at end of file