From d1c0b6abdcd02618982c0edd234aab025d945860 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Fri, 20 May 2016 10:44:23 +0200 Subject: [PATCH] Add initial support for extra keys --- .../java/com/termux/app/BackgroundJob.java | 112 ++ .../java/com/termux/app/TermuxActivity.java | 98 +- .../java/com/termux/drawer/DrawerLayout.java | 1722 ----------------- .../drawer/DrawerLayoutCompatApi21.java | 88 - .../com/termux/drawer/ViewDragHelper.java | 1522 --------------- .../java/com/termux/drawer/package-info.java | 10 - app/src/main/res/layout/drawer_layout.xml | 105 +- app/src/main/res/layout/extra_keys_main.xml | 10 + app/src/main/res/layout/extra_keys_right.xml | 10 + 9 files changed, 278 insertions(+), 3399 deletions(-) create mode 100644 app/src/main/java/com/termux/app/BackgroundJob.java delete mode 100644 app/src/main/java/com/termux/drawer/DrawerLayout.java delete mode 100644 app/src/main/java/com/termux/drawer/DrawerLayoutCompatApi21.java delete mode 100644 app/src/main/java/com/termux/drawer/ViewDragHelper.java delete mode 100644 app/src/main/java/com/termux/drawer/package-info.java create mode 100644 app/src/main/res/layout/extra_keys_main.xml create mode 100644 app/src/main/res/layout/extra_keys_right.xml diff --git a/app/src/main/java/com/termux/app/BackgroundJob.java b/app/src/main/java/com/termux/app/BackgroundJob.java new file mode 100644 index 00000000..04e0a8b1 --- /dev/null +++ b/app/src/main/java/com/termux/app/BackgroundJob.java @@ -0,0 +1,112 @@ +package com.termux.app; + +import android.util.Log; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * A background job launched by Termux. + */ +public final class BackgroundJob { + + private static final String LOG_TAG = "termux-background"; + + final Process mProcess; + + public BackgroundJob(File cwd, File fileToExecute, String[] args) throws IOException { + String[] env = buildEnvironment(false, cwd.getAbsolutePath()); + + String[] progArray = new String[args.length+1]; + + mProcess = Runtime.getRuntime().exec(progArray, env, cwd); + + new Thread() { + @Override + public void run() { + while (true) { + try { + int exitCode = mProcess.waitFor(); + if (exitCode == 0) { + Log.i(LOG_TAG, "exited normally"); + return; + } else { + Log.i(LOG_TAG, "exited with exit code: " + exitCode); + } + } catch (InterruptedException e) { + // Ignore. + } + } + } + }.start(); + + new Thread() { + @Override + public void run() { + InputStream stdout = mProcess.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8)); + String line; + try { + // FIXME: Long lines. + while ((line = reader.readLine()) != null) { + Log.i(LOG_TAG, line); + } + } catch (IOException e) { + // Ignore. + } + } + }.start(); + + + new Thread() { + @Override + public void run() { + InputStream stderr = mProcess.getErrorStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(stderr, StandardCharsets.UTF_8)); + String line; + try { + // FIXME: Long lines. + while ((line = reader.readLine()) != null) { + Log.e(LOG_TAG, line); + } + } catch (IOException e) { + // Ignore. + } + } + }; + } + + public String[] buildEnvironment(boolean failSafe, String cwd) { + new File(TermuxService.HOME_PATH).mkdirs(); + + if (cwd == null) cwd = TermuxService.HOME_PATH; + + final String termEnv = "TERM=xterm-256color"; + final String homeEnv = "HOME=" + TermuxService.HOME_PATH; + final String prefixEnv = "PREFIX=" + TermuxService.PREFIX_PATH; + final String androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT"); + final String androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA"); + // EXTERNAL_STORAGE is needed for /system/bin/am to work on at least + // Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3. + final String externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE"); + String[] env; + if (failSafe) { + // Keep the default path so that system binaries can be used in the failsafe session. + final String pathEnv = "PATH=" + System.getenv("PATH"); + return new String[] { termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv, pathEnv, externalStorageEnv }; + } else { + final String ps1Env = "PS1=$ "; + final String ldEnv = "LD_LIBRARY_PATH=" + TermuxService.PREFIX_PATH + "/lib"; + final String langEnv = "LANG=en_US.UTF-8"; + final String pathEnv = "PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets"; + final String pwdEnv = "PWD=" + cwd; + + return new String[] { termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv }; + } + } + +} diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index fb26e145..f9d2c749 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -29,6 +29,9 @@ import android.os.IBinder; import android.os.Vibrator; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v4.widget.DrawerLayout; import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; @@ -52,17 +55,18 @@ import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ArrayAdapter; +import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import com.termux.R; -import com.termux.drawer.DrawerLayout; import com.termux.terminal.TerminalSession; import com.termux.terminal.TerminalSession.SessionChangedCallback; import com.termux.view.TerminalKeyListener; import com.termux.view.TerminalView; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; @@ -165,14 +169,73 @@ public final class TermuxActivity extends Activity implements ServiceConnection // Prevent overdraw: getWindow().getDecorView().setBackground(null); - setContentView(R.layout.drawer_layout); + mSettings = new TermuxPreferences(this); + + setContentView(R.layout.drawer_layout); mTerminalView = (TerminalView) findViewById(R.id.terminal_view); - mSettings = new TermuxPreferences(this); - mTerminalView.setTextSize(mSettings.getFontSize()); + + mTerminalView.setTextSize(mSettings.getFontSize()); mFullScreenHelper.setImmersive(mSettings.isFullScreen()); mTerminalView.requestFocus(); - OnKeyListener keyListener = new OnKeyListener() { + final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager); + if (mSettings.isShowExtraKeys()) viewPager.setVisibility(View.VISIBLE); + + viewPager.setAdapter(new PagerAdapter() { + @Override + public int getCount() { + return 2; + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return view == object; + } + + @Override + public Object instantiateItem(ViewGroup collection, int position) { + LayoutInflater inflater = LayoutInflater.from(TermuxActivity.this); + View layout; + if (position == 0) { + layout = (View) inflater.inflate(R.layout.extra_keys_main, collection, false); + mTerminalView.mModifiers = (TerminalView.KeyboardModifiers) layout; + } else { + layout = (View) inflater.inflate(R.layout.extra_keys_right, collection, false); + final EditText editText = (EditText) layout.findViewById(R.id.text_input); + editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + String s = editText.getText().toString() + "\n"; + getCurrentTermSession().write(s); + editText.setText(""); + return true; + } + }); + } + collection.addView(layout); + return layout; + } + + @Override + public void destroyItem(ViewGroup collection, int position, Object view) { + collection.removeView((View) view); + } + }); + + viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageSelected(int position) { + int newHeight; + if (position == 0) { + mTerminalView.requestFocus(); + } else { + final EditText editText = (EditText) viewPager.findViewById(R.id.text_input); + if (editText != null) editText.requestFocus(); + } + } + }); + + OnKeyListener keyListener = new OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if (event.getAction() != KeyEvent.ACTION_DOWN) return false; @@ -213,7 +276,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection if (--index < 0) index = mTermService.getSessions().size() - 1; switchToSession(mTermService.getSessions().get(index)); } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { - getDrawer().openDrawer(Gravity.START); + getDrawer().openDrawer(Gravity.LEFT); } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { getDrawer().closeDrawers(); } else if (unicodeChar == 'f'/* full screen */) { @@ -315,7 +378,17 @@ public final class TermuxActivity extends Activity implements ServiceConnection } }); - registerForContextMenu(mTerminalView); + findViewById(R.id.toggle_keyboard_button).setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + View extraKeysView = findViewById(R.id.viewpager); + mSettings.toggleShowExtraKeys(TermuxActivity.this); + extraKeysView.setVisibility(mSettings.isShowExtraKeys() ? View.VISIBLE : View.GONE); + return true; + } + }); + + registerForContextMenu(mTerminalView); Intent serviceIntent = new Intent(this, TermuxService.class); // Start the service and make it run regardless of who is bound to it: @@ -362,7 +435,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection finish(); return; } - if (mIsVisible && finishedSession != getCurrentTermSession()) { + if (mIsVisible && finishedSession != getCurrentTermSession()) { // Show toast for non-current sessions that exit. int indexOfSession = mTermService.getSessions().indexOf(finishedSession); // Verify that session was not removed before we got told about it finishing: @@ -543,10 +616,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection @Override public void onBackPressed() { - if (getDrawer().isDrawerOpen(Gravity.START)) - getDrawer().closeDrawers(); - else - finish(); + if (getDrawer().isDrawerOpen(Gravity.LEFT)) { + getDrawer().closeDrawers(); + } else { + finish(); + } } @Override diff --git a/app/src/main/java/com/termux/drawer/DrawerLayout.java b/app/src/main/java/com/termux/drawer/DrawerLayout.java deleted file mode 100644 index 7d3fec0b..00000000 --- a/app/src/main/java/com/termux/drawer/DrawerLayout.java +++ /dev/null @@ -1,1722 +0,0 @@ -package com.termux.drawer; - -/* - * Copyright (C) 2013 The Android Open Source Project - * - * 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. - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.PixelFormat; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityNodeInfo; - -import java.util.List; - -/** - * DrawerLayout acts as a top-level container for window content that allows for interactive "drawer" views to be pulled - * out from the edge of the window. - * - *

- * Drawer positioning and layout is controlled using the android:layout_gravity attribute on child views - * corresponding to which side of the view you want the drawer to emerge from: left or right. (Or start/end on platform - * versions that support layout direction.) - *

- * - *

- * To use a DrawerLayout, position your primary content view as the first child with a width and height of - * match_parent. Add drawers as child views after the main content view and set the - * layout_gravity appropriately. Drawers commonly use match_parent for height with a fixed - * width. - *

- * - *

- * {@link DrawerListener} can be used to monitor the state and motion of drawer views. Avoid performing expensive - * operations such as layout during animation as it can cause stuttering; try to perform expensive operations during the - * {@link #STATE_IDLE} state. {@link SimpleDrawerListener} offers default/no-op implementations of each callback method. - *

- * - *

- * As per the Android Design guide, any drawers - * positioned to the left/start should always contain content for navigating around the application, whereas any drawers - * positioned to the right/end should always contain actions to take on the current content. This preserves the same - * navigation left, actions right structure present in the Action Bar and elsewhere. - *

- * - *

- * For more information about how to use DrawerLayout, read Creating a Navigation Drawer. - *

- */ -@SuppressLint("RtlHardcoded") -public class DrawerLayout extends ViewGroup { - private static final String TAG = "DrawerLayout"; - - /** - * Indicates that any drawers are in an idle, settled state. No animation is in progress. - */ - public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE; - - /** - * Indicates that a drawer is currently being dragged by the user. - */ - public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING; - - /** - * Indicates that a drawer is in the process of settling to a final position. - */ - public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING; - - /** - * The drawer is unlocked. - */ - public static final int LOCK_MODE_UNLOCKED = 0; - - /** - * The drawer is locked closed. The user may not open it, though the app may open it programmatically. - */ - public static final int LOCK_MODE_LOCKED_CLOSED = 1; - - /** - * The drawer is locked open. The user may not close it, though the app may close it programmatically. - */ - public static final int LOCK_MODE_LOCKED_OPEN = 2; - - private static final int MIN_DRAWER_MARGIN = 64; // dp - - private static final int DEFAULT_SCRIM_COLOR = 0x99000000; - - /** - * Length of time to delay before peeking the drawer. - */ - private static final int PEEK_DELAY = 160; // ms - - /** - * Minimum velocity that will be detected as a fling - */ - private static final int MIN_FLING_VELOCITY = 400; // dips per second - - /** - * Experimental feature. - */ - private static final boolean ALLOW_EDGE_LOCK = false; - - private static final boolean CHILDREN_DISALLOW_INTERCEPT = true; - - private static final float TOUCH_SLOP_SENSITIVITY = 1.f; - - static final int[] LAYOUT_ATTRS = new int[] { android.R.attr.layout_gravity }; - - /** Whether we can use NO_HIDE_DESCENDANTS accessibility importance. */ - static final boolean CAN_HIDE_DESCENDANTS = Build.VERSION.SDK_INT >= 19; - - private final ChildAccessibilityDelegate mChildAccessibilityDelegate = new ChildAccessibilityDelegate(); - - private final int mMinDrawerMargin; - - private int mScrimColor = DEFAULT_SCRIM_COLOR; - private float mScrimOpacity; - private final Paint mScrimPaint = new Paint(); - - private final ViewDragHelper mLeftDragger; - private final ViewDragHelper mRightDragger; - private final ViewDragCallback mLeftCallback; - private final ViewDragCallback mRightCallback; - private int mDrawerState; - private boolean mInLayout; - private boolean mFirstLayout = true; - private int mLockModeLeft; - private int mLockModeRight; - private boolean mChildrenCanceledTouch; - - private DrawerListener mListener; - - private float mInitialMotionX; - private float mInitialMotionY; - - private Drawable mShadowLeft; - private Drawable mShadowRight; - private Drawable mStatusBarBackground; - - private CharSequence mTitleLeft; - private CharSequence mTitleRight; - - private Object mLastInsets; - private boolean mDrawStatusBarBackground; - - /** - * Listener for monitoring events about drawers. - */ - public interface DrawerListener { - /** - * Called when a drawer's position changes. - * - * @param drawerView - * The child view that was moved - * @param slideOffset - * The new offset of this drawer within its range, from 0-1 - */ - void onDrawerSlide(View drawerView, float slideOffset); - - /** - * Called when a drawer has settled in a completely open state. The drawer is interactive at this point. - * - * @param drawerView - * Drawer view that is now open - */ - void onDrawerOpened(View drawerView); - - /** - * Called when a drawer has settled in a completely closed state. - * - * @param drawerView - * Drawer view that is now closed - */ - void onDrawerClosed(View drawerView); - - /** - * Called when the drawer motion state changes. The new state will be one of {@link #STATE_IDLE}, - * {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. - * - * @param newState - * The new drawer motion state - */ - void onDrawerStateChanged(int newState); - } - - /** - * Stub/no-op implementations of all methods of {@link DrawerListener}. Override this if you only care about a few - * of the available callback methods. - */ - public static abstract class SimpleDrawerListener implements DrawerListener { - @Override - public void onDrawerSlide(View drawerView, float slideOffset) { - // Empty. - } - - @Override - public void onDrawerOpened(View drawerView) { - // Empty. - } - - @Override - public void onDrawerClosed(View drawerView) { - // Empty. - } - - @Override - public void onDrawerStateChanged(int newState) { - // Empty. - } - } - - interface DrawerLayoutCompatImpl { - void configureApplyInsets(View drawerLayout); - - void dispatchChildInsets(View child, Object insets, int drawerGravity); - - void applyMarginInsets(MarginLayoutParams lp, Object insets, int drawerGravity); - - int getTopInset(Object lastInsets); - - Drawable getDefaultStatusBarBackground(Context context); - } - - static class DrawerLayoutCompatImplBase implements DrawerLayoutCompatImpl { - @Override - public void configureApplyInsets(View drawerLayout) { - // This space for rent - } - - @Override - public void dispatchChildInsets(View child, Object insets, int drawerGravity) { - // This space for rent - } - - @Override - public void applyMarginInsets(MarginLayoutParams lp, Object insets, int drawerGravity) { - // This space for rent - } - - @Override - public int getTopInset(Object insets) { - return 0; - } - - @Override - public Drawable getDefaultStatusBarBackground(Context context) { - return null; - } - } - - public DrawerLayout(Context context) { - this(context, null); - } - - public DrawerLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public DrawerLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); - final float density = getResources().getDisplayMetrics().density; - mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f); - final float minVel = MIN_FLING_VELOCITY * density; - - mLeftCallback = new ViewDragCallback(Gravity.LEFT); - mRightCallback = new ViewDragCallback(Gravity.RIGHT); - - mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback); - mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT); - mLeftDragger.setMinVelocity(minVel); - mLeftCallback.setDragger(mLeftDragger); - - mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback); - mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT); - mRightDragger.setMinVelocity(minVel); - mRightCallback.setDragger(mRightDragger); - - // So that we can catch the back button - setFocusableInTouchMode(true); - - this.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - - this.setAccessibilityDelegate(new AccessibilityDelegate()); - this.setMotionEventSplittingEnabled(false); - if (this.getFitsSystemWindows()) { - DrawerLayoutCompatApi21.configureApplyInsets(this); - mStatusBarBackground = DrawerLayoutCompatApi21.getDefaultStatusBarBackground(context); - } - } - - /** - * Internal use only; called to apply window insets when configured with fitsSystemWindows="true" - */ - public void setChildInsets(Object insets, boolean draw) { - mLastInsets = insets; - mDrawStatusBarBackground = draw; - setWillNotDraw(!draw && getBackground() == null); - requestLayout(); - } - - /** - * Set a simple drawable used for the left or right shadow. The drawable provided must have a nonzero intrinsic - * width. - * - * @param shadowDrawable - * Shadow drawable to use at the edge of a drawer - * @param gravity - * Which drawer the shadow should apply to - */ - public void setDrawerShadow(Drawable shadowDrawable, int gravity) { - /* - * TODO Someone someday might want to set more complex drawables here. They're probably nuts, but we might want - * to consider registering callbacks, setting states, etc. properly. - */ - - final int absGravity = Gravity.getAbsoluteGravity(gravity, this.getLayoutDirection()); - if ((absGravity & Gravity.LEFT) == Gravity.LEFT) { - mShadowLeft = shadowDrawable; - invalidate(); - } - if ((absGravity & Gravity.RIGHT) == Gravity.RIGHT) { - mShadowRight = shadowDrawable; - invalidate(); - } - } - - /** - * Set a simple drawable used for the left or right shadow. The drawable provided must have a nonzero intrinsic - * width. - * - * @param resId - * Resource id of a shadow drawable to use at the edge of a drawer - * @param gravity - * Which drawer the shadow should apply to - */ - public void setDrawerShadow(int resId, int gravity) { - setDrawerShadow(getResources().getDrawable(resId), gravity); - } - - /** - * Set a color to use for the scrim that obscures primary content while a drawer is open. - * - * @param color - * Color to use in 0xAARRGGBB format. - */ - public void setScrimColor(int color) { - mScrimColor = color; - invalidate(); - } - - /** - * Set a listener to be notified of drawer events. - * - * @param listener - * Listener to notify when drawer events occur - * @see DrawerListener - */ - public void setDrawerListener(DrawerListener listener) { - mListener = listener; - } - - /** - * Enable or disable interaction with all drawers. - * - *

- * This allows the application to restrict the user's ability to open or close any drawer within this layout. - * DrawerLayout will still respond to calls to {@link #openDrawer(int)}, {@link #closeDrawer(int)} and friends if a - * drawer is locked. - *

- * - *

- * Locking drawers open or closed will implicitly open or close any drawers as appropriate. - *

- * - * @param lockMode - * The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, - * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. - */ - public void setDrawerLockMode(int lockMode) { - setDrawerLockMode(lockMode, Gravity.LEFT); - setDrawerLockMode(lockMode, Gravity.RIGHT); - } - - /** - * Enable or disable interaction with the given drawer. - * - *

- * This allows the application to restrict the user's ability to open or close the given drawer. DrawerLayout will - * still respond to calls to {@link #openDrawer(int)}, {@link #closeDrawer(int)} and friends if a drawer is locked. - *

- * - *

- * Locking a drawer open or closed will implicitly open or close that drawer as appropriate. - *

- * - * @param lockMode - * The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, - * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. - * @param edgeGravity - * Gravity.LEFT, RIGHT, START or END. Expresses which drawer to change the mode for. - * - * @see #LOCK_MODE_UNLOCKED - * @see #LOCK_MODE_LOCKED_CLOSED - * @see #LOCK_MODE_LOCKED_OPEN - */ - public void setDrawerLockMode(int lockMode, int edgeGravity) { - final int absGravity = Gravity.getAbsoluteGravity(edgeGravity, this.getLayoutDirection()); - if (absGravity == Gravity.LEFT) { - mLockModeLeft = lockMode; - } else if (absGravity == Gravity.RIGHT) { - mLockModeRight = lockMode; - } - if (lockMode != LOCK_MODE_UNLOCKED) { - // Cancel interaction in progress - final ViewDragHelper helper = absGravity == Gravity.LEFT ? mLeftDragger : mRightDragger; - helper.cancel(); - } - switch (lockMode) { - case LOCK_MODE_LOCKED_OPEN: - final View toOpen = findDrawerWithGravity(absGravity); - if (toOpen != null) { - openDrawer(toOpen); - } - break; - case LOCK_MODE_LOCKED_CLOSED: - final View toClose = findDrawerWithGravity(absGravity); - if (toClose != null) { - closeDrawer(toClose); - } - break; - // default: do nothing - } - } - - /** - * Enable or disable interaction with the given drawer. - * - *

- * This allows the application to restrict the user's ability to open or close the given drawer. DrawerLayout will - * still respond to calls to {@link #openDrawer(int)}, {@link #closeDrawer(int)} and friends if a drawer is locked. - *

- * - *

- * Locking a drawer open or closed will implicitly open or close that drawer as appropriate. - *

- * - * @param lockMode - * The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, - * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. - * @param drawerView - * The drawer view to change the lock mode for - * - * @see #LOCK_MODE_UNLOCKED - * @see #LOCK_MODE_LOCKED_CLOSED - * @see #LOCK_MODE_LOCKED_OPEN - */ - public void setDrawerLockMode(int lockMode, View drawerView) { - if (!isDrawerView(drawerView)) { - throw new IllegalArgumentException("View " + drawerView + " is not a " + "drawer with appropriate layout_gravity"); - } - final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; - setDrawerLockMode(lockMode, gravity); - } - - /** - * Check the lock mode of the drawer with the given gravity. - * - * @param edgeGravity - * Gravity of the drawer to check - * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. - */ - public int getDrawerLockMode(int edgeGravity) { - final int absGravity = Gravity.getAbsoluteGravity(edgeGravity, this.getLayoutDirection()); - if (absGravity == Gravity.LEFT) { - return mLockModeLeft; - } else if (absGravity == Gravity.RIGHT) { - return mLockModeRight; - } - return LOCK_MODE_UNLOCKED; - } - - /** - * Check the lock mode of the given drawer view. - * - * @param drawerView - * Drawer view to check lock mode - * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. - */ - public int getDrawerLockMode(View drawerView) { - final int absGravity = getDrawerViewAbsoluteGravity(drawerView); - if (absGravity == Gravity.LEFT) { - return mLockModeLeft; - } else if (absGravity == Gravity.RIGHT) { - return mLockModeRight; - } - return LOCK_MODE_UNLOCKED; - } - - /** - * Sets the title of the drawer with the given gravity. - *

- * When accessibility is turned on, this is the title that will be used to identify the drawer to the active - * accessibility service. - * - * @param edgeGravity - * Gravity.LEFT, RIGHT, START or END. Expresses which drawer to set the title for. - * @param title - * The title for the drawer. - */ - public void setDrawerTitle(int edgeGravity, CharSequence title) { - final int absGravity = Gravity.getAbsoluteGravity(edgeGravity, this.getLayoutDirection()); - if (absGravity == Gravity.LEFT) { - mTitleLeft = title; - } else if (absGravity == Gravity.RIGHT) { - mTitleRight = title; - } - } - - /** - * Returns the title of the drawer with the given gravity. - * - * @param edgeGravity - * Gravity.LEFT, RIGHT, START or END. Expresses which drawer to return the title for. - * @return The title of the drawer, or null if none set. - * @see #setDrawerTitle(int, CharSequence) - */ - public CharSequence getDrawerTitle(int edgeGravity) { - final int absGravity = Gravity.getAbsoluteGravity(edgeGravity, this.getLayoutDirection()); - if (absGravity == Gravity.LEFT) { - return mTitleLeft; - } else if (absGravity == Gravity.RIGHT) { - return mTitleRight; - } - return null; - } - - /** - * Resolve the shared state of all drawers from the component ViewDragHelpers. Should be called whenever a - * ViewDragHelper's state changes. - */ - void updateDrawerState(int activeState, View activeDrawer) { - final int leftState = mLeftDragger.getViewDragState(); - final int rightState = mRightDragger.getViewDragState(); - - final int state; - if (leftState == STATE_DRAGGING || rightState == STATE_DRAGGING) { - state = STATE_DRAGGING; - } else if (leftState == STATE_SETTLING || rightState == STATE_SETTLING) { - state = STATE_SETTLING; - } else { - state = STATE_IDLE; - } - - if (activeDrawer != null && activeState == STATE_IDLE) { - final LayoutParams lp = (LayoutParams) activeDrawer.getLayoutParams(); - if (lp.onScreen == 0) { - dispatchOnDrawerClosed(activeDrawer); - } else if (lp.onScreen == 1) { - dispatchOnDrawerOpened(activeDrawer); - } - } - - if (state != mDrawerState) { - mDrawerState = state; - - if (mListener != null) { - mListener.onDrawerStateChanged(state); - } - } - } - - void dispatchOnDrawerClosed(View drawerView) { - final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - if (lp.knownOpen) { - lp.knownOpen = false; - if (mListener != null) { - mListener.onDrawerClosed(drawerView); - } - - updateChildrenImportantForAccessibility(drawerView, false); - - // Only send WINDOW_STATE_CHANGE if the host has window focus. This - // may change if support for multiple foreground windows (e.g. IME) - // improves. - if (hasWindowFocus()) { - final View rootView = getRootView(); - if (rootView != null) { - rootView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - } - } - } - } - - void dispatchOnDrawerOpened(View drawerView) { - final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - if (!lp.knownOpen) { - lp.knownOpen = true; - if (mListener != null) { - mListener.onDrawerOpened(drawerView); - } - - updateChildrenImportantForAccessibility(drawerView, true); - - // Only send WINDOW_STATE_CHANGE if the host has window focus. - if (hasWindowFocus()) { - sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - } - - drawerView.requestFocus(); - } - } - - private void updateChildrenImportantForAccessibility(View drawerView, boolean isDrawerOpen) { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (!isDrawerOpen && !isDrawerView(child) || isDrawerOpen && child == drawerView) { - // Drawer is closed and this is a content view or this is an - // open drawer view, so it should be visible. - child.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } else { - child.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - } - } - } - - void dispatchOnDrawerSlide(View drawerView, float slideOffset) { - if (mListener != null) { - mListener.onDrawerSlide(drawerView, slideOffset); - } - } - - void setDrawerViewOffset(View drawerView, float slideOffset) { - final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - if (slideOffset == lp.onScreen) { - return; - } - - lp.onScreen = slideOffset; - dispatchOnDrawerSlide(drawerView, slideOffset); - } - - float getDrawerViewOffset(View drawerView) { - return ((LayoutParams) drawerView.getLayoutParams()).onScreen; - } - - /** - * @return the absolute gravity of the child drawerView, resolved according to the current layout direction - */ - int getDrawerViewAbsoluteGravity(View drawerView) { - final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; - return Gravity.getAbsoluteGravity(gravity, this.getLayoutDirection()); - } - - boolean checkDrawerViewAbsoluteGravity(View drawerView, int checkFor) { - final int absGravity = getDrawerViewAbsoluteGravity(drawerView); - return (absGravity & checkFor) == checkFor; - } - - View findOpenDrawer() { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (((LayoutParams) child.getLayoutParams()).knownOpen) { - return child; - } - } - return null; - } - - void moveDrawerToOffset(View drawerView, float slideOffset) { - final float oldOffset = getDrawerViewOffset(drawerView); - final int width = drawerView.getWidth(); - final int oldPos = (int) (width * oldOffset); - final int newPos = (int) (width * slideOffset); - final int dx = newPos - oldPos; - - drawerView.offsetLeftAndRight(checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT) ? dx : -dx); - setDrawerViewOffset(drawerView, slideOffset); - } - - /** - * @param gravity - * the gravity of the child to return. If specified as a relative value, it will be resolved according to - * the current layout direction. - * @return the drawer with the specified gravity - */ - View findDrawerWithGravity(int gravity) { - final int absHorizGravity = Gravity.getAbsoluteGravity(gravity, this.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK; - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final int childAbsGravity = getDrawerViewAbsoluteGravity(child); - if ((childAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == absHorizGravity) { - return child; - } - } - return null; - } - - /** - * Simple gravity to string - only supports LEFT and RIGHT for debugging output. - * - * @param gravity - * Absolute gravity value - * @return LEFT or RIGHT as appropriate, or a hex string - */ - static String gravityToString(int gravity) { - if ((gravity & Gravity.LEFT) == Gravity.LEFT) { - return "LEFT"; - } - if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) { - return "RIGHT"; - } - return Integer.toHexString(gravity); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mFirstLayout = true; - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mFirstLayout = true; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int widthMode = MeasureSpec.getMode(widthMeasureSpec); - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int widthSize = MeasureSpec.getSize(widthMeasureSpec); - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - - if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) { - if (isInEditMode()) { - // Don't crash the layout editor. Consume all of the space if specified - // or pick a magic number from thin air otherwise. - // TODO Better communication with tools of this bogus state. - // It will crash on a real device. - if (widthMode == MeasureSpec.UNSPECIFIED) { - widthSize = 300; - } - if (heightMode == MeasureSpec.UNSPECIFIED) { - heightSize = 300; - } - } else { - throw new IllegalArgumentException("DrawerLayout must be measured with MeasureSpec.EXACTLY."); - } - } - - setMeasuredDimension(widthSize, heightSize); - - final boolean applyInsets = mLastInsets != null && this.getFitsSystemWindows(); - final int layoutDirection = this.getLayoutDirection(); - - // Gravity value for each drawer we've seen. Only one of each permitted. - int foundDrawers = 0; - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - - if (child.getVisibility() == GONE) { - continue; - } - - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - if (applyInsets) { - final int cgrav = Gravity.getAbsoluteGravity(lp.gravity, layoutDirection); - if (child.getFitsSystemWindows()) { - DrawerLayoutCompatApi21.dispatchChildInsets(child, mLastInsets, cgrav); - } else { - DrawerLayoutCompatApi21.applyMarginInsets(lp, mLastInsets, cgrav); - } - } - - if (isContentView(child)) { - // Content views get measured at exactly the layout's size. - final int contentWidthSpec = MeasureSpec.makeMeasureSpec(widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); - final int contentHeightSpec = MeasureSpec.makeMeasureSpec(heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); - child.measure(contentWidthSpec, contentHeightSpec); - } else if (isDrawerView(child)) { - final int childGravity = getDrawerViewAbsoluteGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK; - if ((foundDrawers & childGravity) != 0) { - throw new IllegalStateException("Child drawer has absolute gravity " + gravityToString(childGravity) + " but this " + TAG - + " already has a " + "drawer view along that edge"); - } - final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, mMinDrawerMargin + lp.leftMargin + lp.rightMargin, lp.width); - final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec, lp.topMargin + lp.bottomMargin, lp.height); - child.measure(drawerWidthSpec, drawerHeightSpec); - } else { - throw new IllegalStateException("Child " + child + " at index " + i + " does not have a valid layout_gravity - must be Gravity.LEFT, " - + "Gravity.RIGHT or Gravity.NO_GRAVITY"); - } - } - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - mInLayout = true; - final int width = r - l; - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - - if (child.getVisibility() == GONE) { - continue; - } - - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - if (isContentView(child)) { - child.layout(lp.leftMargin, lp.topMargin, lp.leftMargin + child.getMeasuredWidth(), lp.topMargin + child.getMeasuredHeight()); - } else { // Drawer, if it wasn't onMeasure would have thrown an exception. - final int childWidth = child.getMeasuredWidth(); - final int childHeight = child.getMeasuredHeight(); - int childLeft; - - final float newOffset; - if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { - childLeft = -childWidth + (int) (childWidth * lp.onScreen); - newOffset = (float) (childWidth + childLeft) / childWidth; - } else { // Right; onMeasure checked for us. - childLeft = width - (int) (childWidth * lp.onScreen); - newOffset = (float) (width - childLeft) / childWidth; - } - - final boolean changeOffset = newOffset != lp.onScreen; - - final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; - - switch (vgrav) { - default: - case Gravity.TOP: { - child.layout(childLeft, lp.topMargin, childLeft + childWidth, lp.topMargin + childHeight); - break; - } - - case Gravity.BOTTOM: { - final int height = b - t; - child.layout(childLeft, height - lp.bottomMargin - child.getMeasuredHeight(), childLeft + childWidth, height - lp.bottomMargin); - break; - } - - case Gravity.CENTER_VERTICAL: { - final int height = b - t; - int childTop = (height - childHeight) / 2; - - // Offset for margins. If things don't fit right because of - // bad measurement before, oh well. - if (childTop < lp.topMargin) { - childTop = lp.topMargin; - } else if (childTop + childHeight > height - lp.bottomMargin) { - childTop = height - lp.bottomMargin - childHeight; - } - child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); - break; - } - } - - if (changeOffset) { - setDrawerViewOffset(child, newOffset); - } - - final int newVisibility = lp.onScreen > 0 ? VISIBLE : INVISIBLE; - if (child.getVisibility() != newVisibility) { - child.setVisibility(newVisibility); - } - } - } - mInLayout = false; - mFirstLayout = false; - } - - @Override - public void requestLayout() { - if (!mInLayout) { - super.requestLayout(); - } - } - - @Override - public void computeScroll() { - final int childCount = getChildCount(); - float scrimOpacity = 0; - for (int i = 0; i < childCount; i++) { - final float onscreen = ((LayoutParams) getChildAt(i).getLayoutParams()).onScreen; - scrimOpacity = Math.max(scrimOpacity, onscreen); - } - mScrimOpacity = scrimOpacity; - - // "|" used on purpose; both need to run. - if (mLeftDragger.continueSettling(true) | mRightDragger.continueSettling(true)) { - this.postInvalidateOnAnimation(); - } - } - - private static boolean hasOpaqueBackground(View v) { - final Drawable bg = v.getBackground(); - return bg != null && bg.getOpacity() == PixelFormat.OPAQUE; - } - - /** - * Set a drawable to draw in the insets area for the status bar. Note that this will only be activated if this - * DrawerLayout fitsSystemWindows. - * - * @param bg - * Background drawable to draw behind the status bar - */ - public void setStatusBarBackground(Drawable bg) { - mStatusBarBackground = bg; - invalidate(); - } - - /** - * Gets the drawable used to draw in the insets area for the status bar. - * - * @return The status bar background drawable, or null if none set - */ - public Drawable getStatusBarBackgroundDrawable() { - return mStatusBarBackground; - } - - /** - * Set a drawable to draw in the insets area for the status bar. Note that this will only be activated if this - * DrawerLayout fitsSystemWindows. - * - * @param resId - * Resource id of a background drawable to draw behind the status bar - */ - public void setStatusBarBackground(int resId) { - mStatusBarBackground = resId != 0 ? getContext().getDrawable(resId) : null; - invalidate(); - } - - /** - * Set a drawable to draw in the insets area for the status bar. Note that this will only be activated if this - * DrawerLayout fitsSystemWindows. - * - * @param color - * Color to use as a background drawable to draw behind the status bar in 0xAARRGGBB format. - */ - public void setStatusBarBackgroundColor(int color) { - mStatusBarBackground = new ColorDrawable(color); - invalidate(); - } - - @Override - public void onDraw(Canvas c) { - super.onDraw(c); - if (mDrawStatusBarBackground && mStatusBarBackground != null) { - final int inset = DrawerLayoutCompatApi21.getTopInset(mLastInsets); - if (inset > 0) { - mStatusBarBackground.setBounds(0, 0, getWidth(), inset); - mStatusBarBackground.draw(c); - } - } - } - - @Override - protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - final int height = getHeight(); - final boolean drawingContent = isContentView(child); - int clipLeft = 0, clipRight = getWidth(); - - final int restoreCount = canvas.save(); - if (drawingContent) { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View v = getChildAt(i); - if (v == child || v.getVisibility() != VISIBLE || !hasOpaqueBackground(v) || !isDrawerView(v) || v.getHeight() < height) { - continue; - } - - if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) { - final int vright = v.getRight(); - if (vright > clipLeft) clipLeft = vright; - } else { - final int vleft = v.getLeft(); - if (vleft < clipRight) clipRight = vleft; - } - } - canvas.clipRect(clipLeft, 0, clipRight, getHeight()); - } - final boolean result = super.drawChild(canvas, child, drawingTime); - canvas.restoreToCount(restoreCount); - - if (mScrimOpacity > 0 && drawingContent) { - final int baseAlpha = (mScrimColor & 0xff000000) >>> 24; - final int imag = (int) (baseAlpha * mScrimOpacity); - final int color = imag << 24 | (mScrimColor & 0xffffff); - mScrimPaint.setColor(color); - - canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint); - } else if (mShadowLeft != null && checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { - final int shadowWidth = mShadowLeft.getIntrinsicWidth(); - final int childRight = child.getRight(); - final int drawerPeekDistance = mLeftDragger.getEdgeSize(); - final float alpha = Math.max(0, Math.min((float) childRight / drawerPeekDistance, 1.f)); - mShadowLeft.setBounds(childRight, child.getTop(), childRight + shadowWidth, child.getBottom()); - mShadowLeft.setAlpha((int) (0xff * alpha)); - mShadowLeft.draw(canvas); - } else if (mShadowRight != null && checkDrawerViewAbsoluteGravity(child, Gravity.RIGHT)) { - final int shadowWidth = mShadowRight.getIntrinsicWidth(); - final int childLeft = child.getLeft(); - final int showing = getWidth() - childLeft; - final int drawerPeekDistance = mRightDragger.getEdgeSize(); - final float alpha = Math.max(0, Math.min((float) showing / drawerPeekDistance, 1.f)); - mShadowRight.setBounds(childLeft - shadowWidth, child.getTop(), childLeft, child.getBottom()); - mShadowRight.setAlpha((int) (0xff * alpha)); - mShadowRight.draw(canvas); - } - return result; - } - - boolean isContentView(View child) { - return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY; - } - - boolean isDrawerView(View child) { - final int gravity = ((LayoutParams) child.getLayoutParams()).gravity; - final int absGravity = Gravity.getAbsoluteGravity(gravity, child.getLayoutDirection()); - return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - final int action = ev.getActionMasked(); - - // "|" used deliberately here; both methods should be invoked. - final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) | mRightDragger.shouldInterceptTouchEvent(ev); - - boolean interceptForTap = false; - - switch (action) { - case MotionEvent.ACTION_DOWN: { - final float x = ev.getX(); - final float y = ev.getY(); - mInitialMotionX = x; - mInitialMotionY = y; - if (mScrimOpacity > 0) { - final View child = mLeftDragger.findTopChildUnder((int) x, (int) y); - if (child != null && isContentView(child)) { - interceptForTap = true; - } - } - mChildrenCanceledTouch = false; - break; - } - - case MotionEvent.ACTION_MOVE: { - // If we cross the touch slop, don't perform the delayed peek for an edge touch. - if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) { - mLeftCallback.removeCallbacks(); - mRightCallback.removeCallbacks(); - } - break; - } - - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: { - closeDrawers(true); - mChildrenCanceledTouch = false; - } - } - - return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch; - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(MotionEvent ev) { - mLeftDragger.processTouchEvent(ev); - mRightDragger.processTouchEvent(ev); - - final int action = ev.getAction(); - - switch (action & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: { - final float x = ev.getX(); - final float y = ev.getY(); - mInitialMotionX = x; - mInitialMotionY = y; - mChildrenCanceledTouch = false; - break; - } - - case MotionEvent.ACTION_UP: { - final float x = ev.getX(); - final float y = ev.getY(); - boolean peekingOnly = true; - final View touchedView = mLeftDragger.findTopChildUnder((int) x, (int) y); - if (touchedView != null && isContentView(touchedView)) { - final float dx = x - mInitialMotionX; - final float dy = y - mInitialMotionY; - final int slop = mLeftDragger.getTouchSlop(); - if (dx * dx + dy * dy < slop * slop) { - // Taps close a dimmed open drawer but only if it isn't locked open. - final View openDrawer = findOpenDrawer(); - if (openDrawer != null) { - peekingOnly = getDrawerLockMode(openDrawer) == LOCK_MODE_LOCKED_OPEN; - } - } - } - closeDrawers(peekingOnly); - break; - } - - case MotionEvent.ACTION_CANCEL: { - closeDrawers(true); - mChildrenCanceledTouch = false; - break; - } - } - - return true; - } - - @Override - public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { - if (CHILDREN_DISALLOW_INTERCEPT/* - * || (!mLeftDragger.isEdgeTouched(ViewDragHelper.EDGE_LEFT) && - * !mRightDragger.isEdgeTouched(ViewDragHelper.EDGE_RIGHT)) - */) { - // If we have an edge touch we want to skip this and track it for later instead. - super.requestDisallowInterceptTouchEvent(disallowIntercept); - } - if (disallowIntercept) { - closeDrawers(true); - } - } - - /** - * Close all currently open drawer views by animating them out of view. - */ - public void closeDrawers() { - closeDrawers(false); - } - - void closeDrawers(boolean peekingOnly) { - boolean needsInvalidate = false; - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - if (!isDrawerView(child) || (peekingOnly && !lp.isPeeking)) { - continue; - } - - final int childWidth = child.getWidth(); - - if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { - needsInvalidate |= mLeftDragger.smoothSlideViewTo(child, -childWidth, child.getTop()); - } else { - needsInvalidate |= mRightDragger.smoothSlideViewTo(child, getWidth(), child.getTop()); - } - - lp.isPeeking = false; - } - - mLeftCallback.removeCallbacks(); - mRightCallback.removeCallbacks(); - - if (needsInvalidate) { - invalidate(); - } - } - - /** - * Open the specified drawer view by animating it into view. - * - * @param drawerView - * Drawer view to open - */ - public void openDrawer(View drawerView) { - if (!isDrawerView(drawerView)) { - throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); - } - - if (mFirstLayout) { - final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - lp.onScreen = 1.f; - lp.knownOpen = true; - - updateChildrenImportantForAccessibility(drawerView, true); - } else { - if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { - mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop()); - } else { - mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(), drawerView.getTop()); - } - } - invalidate(); - } - - /** - * Open the specified drawer by animating it out of view. - * - * @param gravity - * Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. Gravity.START or Gravity.END may - * also be used. - */ - public void openDrawer(int gravity) { - final View drawerView = findDrawerWithGravity(gravity); - if (drawerView == null) { - throw new IllegalArgumentException("No drawer view found with gravity " + gravityToString(gravity)); - } - openDrawer(drawerView); - } - - /** - * Close the specified drawer view by animating it into view. - * - * @param drawerView - * Drawer view to close - */ - public void closeDrawer(View drawerView) { - if (!isDrawerView(drawerView)) { - throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); - } - - if (mFirstLayout) { - final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - lp.onScreen = 0.f; - lp.knownOpen = false; - } else { - if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { - mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(), drawerView.getTop()); - } else { - mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop()); - } - } - invalidate(); - } - - /** - * Close the specified drawer by animating it out of view. - * - * @param gravity - * Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. Gravity.START or Gravity.END may - * also be used. - */ - public void closeDrawer(int gravity) { - final View drawerView = findDrawerWithGravity(gravity); - if (drawerView == null) { - throw new IllegalArgumentException("No drawer view found with gravity " + gravityToString(gravity)); - } - closeDrawer(drawerView); - } - - /** - * Check if the given drawer view is currently in an open state. To be considered "open" the drawer must have - * settled into its fully visible state. To check for partial visibility use - * {@link #isDrawerVisible(android.view.View)}. - * - * @param drawer - * Drawer view to check - * @return true if the given drawer view is in an open state - * @see #isDrawerVisible(android.view.View) - */ - public boolean isDrawerOpen(View drawer) { - if (!isDrawerView(drawer)) { - throw new IllegalArgumentException("View " + drawer + " is not a drawer"); - } - return ((LayoutParams) drawer.getLayoutParams()).knownOpen; - } - - /** - * Check if the given drawer view is currently in an open state. To be considered "open" the drawer must have - * settled into its fully visible state. If there is no drawer with the given gravity this method will return false. - * - * @param drawerGravity - * Gravity of the drawer to check - * @return true if the given drawer view is in an open state - */ - public boolean isDrawerOpen(int drawerGravity) { - final View drawerView = findDrawerWithGravity(drawerGravity); - return drawerView != null && isDrawerOpen(drawerView); - } - - /** - * Check if a given drawer view is currently visible on-screen. The drawer may be only peeking onto the screen, - * fully extended, or anywhere inbetween. - * - * @param drawer - * Drawer view to check - * @return true if the given drawer is visible on-screen - * @see #isDrawerOpen(android.view.View) - */ - public boolean isDrawerVisible(View drawer) { - if (!isDrawerView(drawer)) { - throw new IllegalArgumentException("View " + drawer + " is not a drawer"); - } - return ((LayoutParams) drawer.getLayoutParams()).onScreen > 0; - } - - /** - * Check if a given drawer view is currently visible on-screen. The drawer may be only peeking onto the screen, - * fully extended, or anywhere in between. If there is no drawer with the given gravity this method will return - * false. - * - * @param drawerGravity - * Gravity of the drawer to check - * @return true if the given drawer is visible on-screen - */ - public boolean isDrawerVisible(int drawerGravity) { - final View drawerView = findDrawerWithGravity(drawerGravity); - return drawerView != null && isDrawerVisible(drawerView); - } - - private boolean hasPeekingDrawer() { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); - if (lp.isPeeking) { - return true; - } - } - return false; - } - - @Override - protected ViewGroup.LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(android.view.ViewGroup.LayoutParams.MATCH_PARENT, android.view.ViewGroup.LayoutParams.MATCH_PARENT); - } - - @Override - protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof LayoutParams ? new LayoutParams((LayoutParams) p) : p instanceof ViewGroup.MarginLayoutParams ? new LayoutParams( - (MarginLayoutParams) p) : new LayoutParams(p); - } - - @Override - protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof LayoutParams && super.checkLayoutParams(p); - } - - @Override - public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { - return new LayoutParams(getContext(), attrs); - } - - private boolean hasVisibleDrawer() { - return findVisibleDrawer() != null; - } - - View findVisibleDrawer() { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (isDrawerView(child) && isDrawerVisible(child)) { - return child; - } - } - return null; - } - - void cancelChildViewTouch() { - // Cancel child touches - if (!mChildrenCanceledTouch) { - final long now = SystemClock.uptimeMillis(); - final MotionEvent cancelEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - getChildAt(i).dispatchTouchEvent(cancelEvent); - } - cancelEvent.recycle(); - mChildrenCanceledTouch = true; - } - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) { - event.startTracking(); - return true; - } - return super.onKeyDown(keyCode, event); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK) { - final View visibleDrawer = findVisibleDrawer(); - if (visibleDrawer != null && getDrawerLockMode(visibleDrawer) == LOCK_MODE_UNLOCKED) { - closeDrawers(); - } - return visibleDrawer != null; - } - return super.onKeyUp(keyCode, event); - } - - @Override - public void addView(View child, int index, ViewGroup.LayoutParams params) { - super.addView(child, index, params); - - final View openDrawer = findOpenDrawer(); - if (openDrawer != null || isDrawerView(child)) { - // A drawer is already open or the new view is a drawer, so the - // new view should start out hidden. - child.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - } else { - // Otherwise this is a content view and no drawer is open, so the - // new view should start out visible. - child.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - - // We only need a delegate here if the framework doesn't understand - // NO_HIDE_DESCENDANTS importance. - if (!CAN_HIDE_DESCENDANTS) { - child.setAccessibilityDelegate(mChildAccessibilityDelegate); - } - } - - static boolean includeChildForAccessibility(View child) { - // If the child is not important for accessibility we make - // sure this hides the entire subtree rooted at it as the - // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDATS is not - // supported on older platforms but we want to hide the entire - // content and not opened drawers if a drawer is opened. - return child.getImportantForAccessibility() != View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - && child.getImportantForAccessibility() != View.IMPORTANT_FOR_ACCESSIBILITY_NO; - } - - private class ViewDragCallback extends ViewDragHelper.Callback { - private final int mAbsGravity; - private ViewDragHelper mDragger; - - private final Runnable mPeekRunnable = new Runnable() { - @Override - public void run() { - peekDrawer(); - } - }; - - public ViewDragCallback(int gravity) { - mAbsGravity = gravity; - } - - public void setDragger(ViewDragHelper dragger) { - mDragger = dragger; - } - - public void removeCallbacks() { - DrawerLayout.this.removeCallbacks(mPeekRunnable); - } - - @Override - public boolean tryCaptureView(View child, int pointerId) { - // Only capture views where the gravity matches what we're looking for. - // This lets us use two ViewDragHelpers, one for each side drawer. - return isDrawerView(child) && checkDrawerViewAbsoluteGravity(child, mAbsGravity) && getDrawerLockMode(child) == LOCK_MODE_UNLOCKED; - } - - @Override - public void onViewDragStateChanged(int state) { - updateDrawerState(state, mDragger.getCapturedView()); - } - - @Override - public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { - float offset; - final int childWidth = changedView.getWidth(); - - // This reverses the positioning shown in onLayout. - if (checkDrawerViewAbsoluteGravity(changedView, Gravity.LEFT)) { - offset = (float) (childWidth + left) / childWidth; - } else { - final int width = getWidth(); - offset = (float) (width - left) / childWidth; - } - setDrawerViewOffset(changedView, offset); - changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE); - invalidate(); - } - - @Override - public void onViewCaptured(View capturedChild, int activePointerId) { - final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams(); - lp.isPeeking = false; - - closeOtherDrawer(); - } - - private void closeOtherDrawer() { - final int otherGrav = mAbsGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT; - final View toClose = findDrawerWithGravity(otherGrav); - if (toClose != null) { - closeDrawer(toClose); - } - } - - @Override - public void onViewReleased(View releasedChild, float xvel, float yvel) { - // Offset is how open the drawer is, therefore left/right values - // are reversed from one another. - final float offset = getDrawerViewOffset(releasedChild); - final int childWidth = releasedChild.getWidth(); - - int left; - if (checkDrawerViewAbsoluteGravity(releasedChild, Gravity.LEFT)) { - left = xvel > 0 || xvel == 0 && offset > 0.5f ? 0 : -childWidth; - } else { - final int width = getWidth(); - left = xvel < 0 || xvel == 0 && offset > 0.5f ? width - childWidth : width; - } - - mDragger.settleCapturedViewAt(left, releasedChild.getTop()); - invalidate(); - } - - @Override - public void onEdgeTouched(int edgeFlags, int pointerId) { - postDelayed(mPeekRunnable, PEEK_DELAY); - } - - void peekDrawer() { - final View toCapture; - final int childLeft; - final int peekDistance = mDragger.getEdgeSize(); - final boolean leftEdge = mAbsGravity == Gravity.LEFT; - if (leftEdge) { - toCapture = findDrawerWithGravity(Gravity.LEFT); - childLeft = (toCapture != null ? -toCapture.getWidth() : 0) + peekDistance; - } else { - toCapture = findDrawerWithGravity(Gravity.RIGHT); - childLeft = getWidth() - peekDistance; - } - // Only peek if it would mean making the drawer more visible and the drawer isn't locked - if (toCapture != null && ((leftEdge && toCapture.getLeft() < childLeft) || (!leftEdge && toCapture.getLeft() > childLeft)) - && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) { - final LayoutParams lp = (LayoutParams) toCapture.getLayoutParams(); - mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop()); - lp.isPeeking = true; - invalidate(); - - closeOtherDrawer(); - - cancelChildViewTouch(); - } - } - - @Override - public boolean onEdgeLock(int edgeFlags) { - if (ALLOW_EDGE_LOCK) { - final View drawer = findDrawerWithGravity(mAbsGravity); - if (drawer != null && !isDrawerOpen(drawer)) { - closeDrawer(drawer); - } - return true; - } - return false; - } - - @Override - public void onEdgeDragStarted(int edgeFlags, int pointerId) { - final View toCapture; - if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) { - toCapture = findDrawerWithGravity(Gravity.LEFT); - } else { - toCapture = findDrawerWithGravity(Gravity.RIGHT); - } - - if (toCapture != null && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) { - mDragger.captureChildView(toCapture, pointerId); - } - } - - @Override - public int getViewHorizontalDragRange(View child) { - return isDrawerView(child) ? child.getWidth() : 0; - } - - @Override - public int clampViewPositionHorizontal(View child, int left, int dx) { - if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { - return Math.max(-child.getWidth(), Math.min(left, 0)); - } else { - final int width = getWidth(); - return Math.max(width - child.getWidth(), Math.min(left, width)); - } - } - - @Override - public int clampViewPositionVertical(View child, int top, int dy) { - return child.getTop(); - } - } - - public static class LayoutParams extends ViewGroup.MarginLayoutParams { - - public int gravity = Gravity.NO_GRAVITY; - float onScreen; - boolean isPeeking; - boolean knownOpen; - - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - - final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS); - this.gravity = a.getInt(0, Gravity.NO_GRAVITY); - a.recycle(); - } - - public LayoutParams(int width, int height) { - super(width, height); - } - - public LayoutParams(int width, int height, int gravity) { - this(width, height); - this.gravity = gravity; - } - - public LayoutParams(LayoutParams source) { - super(source); - this.gravity = source.gravity; - } - - public LayoutParams(ViewGroup.LayoutParams source) { - super(source); - } - - public LayoutParams(ViewGroup.MarginLayoutParams source) { - super(source); - } - } - - class AccessibilityDelegate extends android.view.View.AccessibilityDelegate { - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - - info.setClassName(DrawerLayout.class.getName()); - - // This view reports itself as focusable so that it can intercept - // the back button, but we should prevent this view from reporting - // itself as focusable to accessibility services. - info.setFocusable(false); - info.setFocused(false); - } - - @Override - public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(host, event); - - event.setClassName(DrawerLayout.class.getName()); - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) { - // Special case to handle window state change events. As far as - // accessibility services are concerned, state changes from - // DrawerLayout invalidate the entire contents of the screen (like - // an Activity or Dialog) and they should announce the title of the - // new content. - if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { - final List eventText = event.getText(); - final View visibleDrawer = findVisibleDrawer(); - if (visibleDrawer != null) { - final int edgeGravity = getDrawerViewAbsoluteGravity(visibleDrawer); - final CharSequence title = getDrawerTitle(edgeGravity); - if (title != null) { - eventText.add(title); - } - } - - return true; - } - - return super.dispatchPopulateAccessibilityEvent(host, event); - } - - @Override - public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event) { - return (CAN_HIDE_DESCENDANTS || includeChildForAccessibility(child)) && super.onRequestSendAccessibilityEvent(host, child, event); - } - } - - final class ChildAccessibilityDelegate extends AccessibilityDelegate { - @Override - public void onInitializeAccessibilityNodeInfo(View child, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(child, info); - - if (!includeChildForAccessibility(child)) { - // If we are ignoring the sub-tree rooted at the child, - // break the connection to the rest of the node tree. - // For details refer to includeChildForAccessibility. - info.setParent(null); - } - } - } -} diff --git a/app/src/main/java/com/termux/drawer/DrawerLayoutCompatApi21.java b/app/src/main/java/com/termux/drawer/DrawerLayoutCompatApi21.java deleted file mode 100644 index f3fe0277..00000000 --- a/app/src/main/java/com/termux/drawer/DrawerLayoutCompatApi21.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.termux.drawer; - -/* - * Copyright (C) 2014 The Android Open Source Project - * - * 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. - */ - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowInsets; - -/** - * Provides functionality for DrawerLayout unique to API 21 - */ -@SuppressLint("RtlHardcoded") -@TargetApi(Build.VERSION_CODES.LOLLIPOP) -class DrawerLayoutCompatApi21 { - - private static final int[] THEME_ATTRS = { android.R.attr.colorPrimaryDark }; - - public static void configureApplyInsets(DrawerLayout drawerLayout) { - drawerLayout.setOnApplyWindowInsetsListener(new InsetsListener()); - drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - } - - public static void dispatchChildInsets(View child, Object insets, int gravity) { - WindowInsets wi = (WindowInsets) insets; - if (gravity == Gravity.LEFT) { - wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom()); - } else if (gravity == Gravity.RIGHT) { - wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom()); - } - child.dispatchApplyWindowInsets(wi); - } - - public static void applyMarginInsets(ViewGroup.MarginLayoutParams lp, Object insets, int gravity) { - WindowInsets wi = (WindowInsets) insets; - if (gravity == Gravity.LEFT) { - wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom()); - } else if (gravity == Gravity.RIGHT) { - wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom()); - } - lp.leftMargin = wi.getSystemWindowInsetLeft(); - lp.topMargin = wi.getSystemWindowInsetTop(); - lp.rightMargin = wi.getSystemWindowInsetRight(); - lp.bottomMargin = wi.getSystemWindowInsetBottom(); - } - - public static int getTopInset(Object insets) { - return insets != null ? ((WindowInsets) insets).getSystemWindowInsetTop() : 0; - } - - public static Drawable getDefaultStatusBarBackground(Context context) { - final TypedArray a = context.obtainStyledAttributes(THEME_ATTRS); - try { - return a.getDrawable(0); - } finally { - a.recycle(); - } - } - - static class InsetsListener implements View.OnApplyWindowInsetsListener { - @Override - public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { - final DrawerLayout drawerLayout = (DrawerLayout) v; - drawerLayout.setChildInsets(insets, insets.getSystemWindowInsetTop() > 0); - return insets.consumeSystemWindowInsets(); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/termux/drawer/ViewDragHelper.java b/app/src/main/java/com/termux/drawer/ViewDragHelper.java deleted file mode 100644 index d423ec7c..00000000 --- a/app/src/main/java/com/termux/drawer/ViewDragHelper.java +++ /dev/null @@ -1,1522 +0,0 @@ -package com.termux.drawer; - -/* - * Copyright (C) 2013 The Android Open Source Project - * - * 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. - */ - -import android.content.Context; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.animation.Interpolator; -import android.widget.Scroller; - -import java.util.Arrays; - -/** - * ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number of useful operations and state - * tracking for allowing a user to drag and reposition views within their parent ViewGroup. - */ -public class ViewDragHelper { - - /** - * A null/invalid pointer ID. - */ - public static final int INVALID_POINTER = -1; - - /** - * A view is not currently being dragged or animating as a result of a fling/snap. - */ - public static final int STATE_IDLE = 0; - - /** - * A view is currently being dragged. The position is currently changing as a result of user input or simulated user - * input. - */ - public static final int STATE_DRAGGING = 1; - - /** - * A view is currently settling into place as a result of a fling or predefined non-interactive motion. - */ - public static final int STATE_SETTLING = 2; - - /** - * Edge flag indicating that the left edge should be affected. - */ - public static final int EDGE_LEFT = 1 /*1 << 0*/; - - /** - * Edge flag indicating that the right edge should be affected. - */ - public static final int EDGE_RIGHT = 1 << 1; - - /** - * Edge flag indicating that the top edge should be affected. - */ - public static final int EDGE_TOP = 1 << 2; - - /** - * Edge flag indicating that the bottom edge should be affected. - */ - public static final int EDGE_BOTTOM = 1 << 3; - - /** - * Edge flag set indicating all edges should be affected. - */ - public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM; - - /** - * Indicates that a check should occur along the horizontal axis - */ - public static final int DIRECTION_HORIZONTAL = 1 /*1 << 0*/; - - /** - * Indicates that a check should occur along the vertical axis - */ - public static final int DIRECTION_VERTICAL = 1 << 1; - - /** - * Indicates that a check should occur along all axes - */ - public static final int DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL; - - private static final int EDGE_SIZE = 20; // dp - - private static final int BASE_SETTLE_DURATION = 256; // ms - private static final int MAX_SETTLE_DURATION = 600; // ms - - // Current drag state; idle, dragging or settling - private int mDragState; - - // Distance to travel before a drag may begin - private int mTouchSlop; - - // Last known position/pointer tracking - private int mActivePointerId = INVALID_POINTER; - private float[] mInitialMotionX; - private float[] mInitialMotionY; - private float[] mLastMotionX; - private float[] mLastMotionY; - private int[] mInitialEdgesTouched; - private int[] mEdgeDragsInProgress; - private int[] mEdgeDragsLocked; - private int mPointersDown; - - private VelocityTracker mVelocityTracker; - private float mMaxVelocity; - private float mMinVelocity; - - private int mEdgeSize; - private int mTrackingEdges; - - private Scroller mScroller; - - private final Callback mCallback; - - private View mCapturedView; - private boolean mReleaseInProgress; - - private final ViewGroup mParentView; - - /** - * A Callback is used as a communication channel with the ViewDragHelper back to the parent view using it. - * on*methods are invoked on siginficant events and several accessor methods are expected to provide - * the ViewDragHelper with more information about the state of the parent view upon request. The callback also makes - * decisions governing the range and draggability of child views. - */ - public static abstract class Callback { - /** - * Called when the drag state changes. See the STATE_* constants for more information. - * - * @param state - * The new drag state - * - * @see #STATE_IDLE - * @see #STATE_DRAGGING - * @see #STATE_SETTLING - */ - public void onViewDragStateChanged(int state) { - // Abstract. - } - - /** - * Called when the captured view's position changes as the result of a drag or settle. - * - * @param changedView - * View whose position changed - * @param left - * New X coordinate of the left edge of the view - * @param top - * New Y coordinate of the top edge of the view - * @param dx - * Change in X position from the last call - * @param dy - * Change in Y position from the last call - */ - public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { - // Abstract. - } - - /** - * Called when a child view is captured for dragging or settling. The ID of the pointer currently dragging the - * captured view is supplied. If activePointerId is identified as {@link #INVALID_POINTER} the capture is - * programmatic instead of pointer-initiated. - * - * @param capturedChild - * Child view that was captured - * @param activePointerId - * Pointer id tracking the child capture - */ - public void onViewCaptured(View capturedChild, int activePointerId) { - // Abstract. - } - - /** - * Called when the child view is no longer being actively dragged. The fling velocity is also supplied, if - * relevant. The velocity values may be clamped to system minimums or maximums. - * - *

- * Calling code may decide to fling or otherwise release the view to let it settle into place. It should do so - * using {@link #settleCapturedViewAt(int, int)} or {@link #flingCapturedView(int, int, int, int)}. If the - * Callback invokes one of these methods, the ViewDragHelper will enter {@link #STATE_SETTLING} and the view - * capture will not fully end until it comes to a complete stop. If neither of these methods is invoked before - * onViewReleased returns, the view will stop in place and the ViewDragHelper will return to - * {@link #STATE_IDLE}. - *

- * - * @param releasedChild - * The captured child view now being released - * @param xvel - * X velocity of the pointer as it left the screen in pixels per second. - * @param yvel - * Y velocity of the pointer as it left the screen in pixels per second. - */ - public void onViewReleased(View releasedChild, float xvel, float yvel) { - // Abstract. - } - - /** - * Called when one of the subscribed edges in the parent view has been touched by the user while no child view - * is currently captured. - * - * @param edgeFlags - * A combination of edge flags describing the edge(s) currently touched - * @param pointerId - * ID of the pointer touching the described edge(s) - * @see #EDGE_LEFT - * @see #EDGE_TOP - * @see #EDGE_RIGHT - * @see #EDGE_BOTTOM - */ - public void onEdgeTouched(int edgeFlags, int pointerId) { - // Abstract. - } - - /** - * Called when the given edge may become locked. This can happen if an edge drag was preliminarily rejected - * before beginning, but after {@link #onEdgeTouched(int, int)} was called. This method should return true to - * lock this edge or false to leave it unlocked. The default behavior is to leave edges unlocked. - * - * @param edgeFlags - * A combination of edge flags describing the edge(s) locked - * @return true to lock the edge, false to leave it unlocked - */ - public boolean onEdgeLock(int edgeFlags) { - return false; - } - - /** - * Called when the user has started a deliberate drag away from one of the subscribed edges in the parent view - * while no child view is currently captured. - * - * @param edgeFlags - * A combination of edge flags describing the edge(s) dragged - * @param pointerId - * ID of the pointer touching the described edge(s) - * @see #EDGE_LEFT - * @see #EDGE_TOP - * @see #EDGE_RIGHT - * @see #EDGE_BOTTOM - */ - public void onEdgeDragStarted(int edgeFlags, int pointerId) { - // Abstract. - } - - /** - * Called to determine the Z-order of child views. - * - * @param index - * the ordered position to query for - * @return index of the view that should be ordered at position index - */ - public int getOrderedChildIndex(int index) { - return index; - } - - /** - * Return the magnitude of a draggable child view's horizontal range of motion in pixels. This method should - * return 0 for views that cannot move horizontally. - * - * @param child - * Child view to check - * @return range of horizontal motion in pixels - */ - public int getViewHorizontalDragRange(View child) { - return 0; - } - - /** - * Return the magnitude of a draggable child view's vertical range of motion in pixels. This method should - * return 0 for views that cannot move vertically. - * - * @param child - * Child view to check - * @return range of vertical motion in pixels - */ - public int getViewVerticalDragRange(View child) { - return 0; - } - - /** - * Called when the user's input indicates that they want to capture the given child view with the pointer - * indicated by pointerId. The callback should return true if the user is permitted to drag the given view with - * the indicated pointer. - * - *

- * ViewDragHelper may call this method multiple times for the same view even if the view is already captured; - * this indicates that a new pointer is trying to take control of the view. - *

- * - *

- * If this method returns true, a call to {@link #onViewCaptured(android.view.View, int)} will follow if the - * capture is successful. - *

- * - * @param child - * Child the user is attempting to capture - * @param pointerId - * ID of the pointer attempting the capture - * @return true if capture should be allowed, false otherwise - */ - public abstract boolean tryCaptureView(View child, int pointerId); - - /** - * Restrict the motion of the dragged child view along the horizontal axis. The default implementation does not - * allow horizontal motion; the extending class must override this method and provide the desired clamping. - * - * - * @param child - * Child view being dragged - * @param left - * Attempted motion along the X axis - * @param dx - * Proposed change in position for left - * @return The new clamped position for left - */ - public int clampViewPositionHorizontal(View child, int left, int dx) { - return 0; - } - - /** - * Restrict the motion of the dragged child view along the vertical axis. The default implementation does not - * allow vertical motion; the extending class must override this method and provide the desired clamping. - * - * - * @param child - * Child view being dragged - * @param top - * Attempted motion along the Y axis - * @param dy - * Proposed change in position for top - * @return The new clamped position for top - */ - public int clampViewPositionVertical(View child, int top, int dy) { - return 0; - } - } - - /** - * Interpolator defining the animation curve for mScroller - */ - private static final Interpolator sInterpolator = new Interpolator() { - @Override - public float getInterpolation(float t) { - t -= 1.0f; - return t * t * t * t * t + 1.0f; - } - }; - - private final Runnable mSetIdleRunnable = new Runnable() { - @Override - public void run() { - setDragState(STATE_IDLE); - } - }; - - /** - * Factory method to create a new ViewDragHelper. - * - * @param forParent - * Parent view to monitor - * @param cb - * Callback to provide information and receive events - * @return a new ViewDragHelper instance - */ - public static ViewDragHelper create(ViewGroup forParent, Callback cb) { - return new ViewDragHelper(forParent.getContext(), forParent, cb); - } - - /** - * Factory method to create a new ViewDragHelper. - * - * @param forParent - * Parent view to monitor - * @param sensitivity - * Multiplier for how sensitive the helper should be about detecting the start of a drag. Larger values - * are more sensitive. 1.0f is normal. - * @param cb - * Callback to provide information and receive events - * @return a new ViewDragHelper instance - */ - public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) { - final ViewDragHelper helper = create(forParent, cb); - helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity)); - return helper; - } - - /** - * Apps should use ViewDragHelper.create() to get a new instance. This will allow VDH to use internal compatibility - * implementations for different platform versions. - * - * @param context - * Context to initialize config-dependent params from - * @param forParent - * Parent view to monitor - */ - private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) { - if (forParent == null) { - throw new IllegalArgumentException("Parent view may not be null"); - } - if (cb == null) { - throw new IllegalArgumentException("Callback may not be null"); - } - - mParentView = forParent; - mCallback = cb; - - final ViewConfiguration vc = ViewConfiguration.get(context); - final float density = context.getResources().getDisplayMetrics().density; - mEdgeSize = (int) (EDGE_SIZE * density + 0.5f); - - mTouchSlop = vc.getScaledTouchSlop(); - mMaxVelocity = vc.getScaledMaximumFlingVelocity(); - mMinVelocity = vc.getScaledMinimumFlingVelocity(); - mScroller = new Scroller(context, sInterpolator); - } - - /** - * Set the minimum velocity that will be detected as having a magnitude greater than zero in pixels per second. - * Callback methods accepting a velocity will be clamped appropriately. - * - * @param minVel - * Minimum velocity to detect - */ - public void setMinVelocity(float minVel) { - mMinVelocity = minVel; - } - - /** - * Return the currently configured minimum velocity. Any flings with a magnitude less than this value in pixels per - * second. Callback methods accepting a velocity will receive zero as a velocity value if the real detected velocity - * was below this threshold. - * - * @return the minimum velocity that will be detected - */ - public float getMinVelocity() { - return mMinVelocity; - } - - /** - * Retrieve the current drag state of this helper. This will return one of {@link #STATE_IDLE}, - * {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. - * - * @return The current drag state - */ - public int getViewDragState() { - return mDragState; - } - - /** - * Enable edge tracking for the selected edges of the parent view. The callback's - * {@link Callback#onEdgeTouched(int, int)} and {@link Callback#onEdgeDragStarted(int, int)} methods will only be - * invoked for edges for which edge tracking has been enabled. - * - * @param edgeFlags - * Combination of edge flags describing the edges to watch - * @see #EDGE_LEFT - * @see #EDGE_TOP - * @see #EDGE_RIGHT - * @see #EDGE_BOTTOM - */ - public void setEdgeTrackingEnabled(int edgeFlags) { - mTrackingEdges = edgeFlags; - } - - /** - * Return the size of an edge. This is the range in pixels along the edges of this view that will actively detect - * edge touches or drags if edge tracking is enabled. - * - * @return The size of an edge in pixels - * @see #setEdgeTrackingEnabled(int) - */ - public int getEdgeSize() { - return mEdgeSize; - } - - /** - * Capture a specific child view for dragging within the parent. The callback will be notified but - * {@link Callback#tryCaptureView(android.view.View, int)} will not be asked permission to capture this view. - * - * @param childView - * Child view to capture - * @param activePointerId - * ID of the pointer that is dragging the captured child view - */ - public void captureChildView(View childView, int activePointerId) { - if (childView.getParent() != mParentView) { - throw new IllegalArgumentException("captureChildView: parameter must be a descendant " + "of the ViewDragHelper's tracked parent view (" - + mParentView + ")"); - } - - mCapturedView = childView; - mActivePointerId = activePointerId; - mCallback.onViewCaptured(childView, activePointerId); - setDragState(STATE_DRAGGING); - } - - /** - * @return The currently captured view, or null if no view has been captured. - */ - public View getCapturedView() { - return mCapturedView; - } - - /** - * @return The ID of the pointer currently dragging the captured view, or {@link #INVALID_POINTER}. - */ - public int getActivePointerId() { - return mActivePointerId; - } - - /** - * @return The minimum distance in pixels that the user must travel to initiate a drag - */ - public int getTouchSlop() { - return mTouchSlop; - } - - /** - * The result of a call to this method is equivalent to {@link #processTouchEvent(android.view.MotionEvent)} - * receiving an ACTION_CANCEL event. - */ - public void cancel() { - mActivePointerId = INVALID_POINTER; - clearMotionHistory(); - - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - } - - /** - * {@link #cancel()}, but also abort all motion in progress and snap to the end of any animation. - */ - public void abort() { - cancel(); - if (mDragState == STATE_SETTLING) { - final int oldX = mScroller.getCurrX(); - final int oldY = mScroller.getCurrY(); - mScroller.abortAnimation(); - final int newX = mScroller.getCurrX(); - final int newY = mScroller.getCurrY(); - mCallback.onViewPositionChanged(mCapturedView, newX, newY, newX - oldX, newY - oldY); - } - setDragState(STATE_IDLE); - } - - /** - * Animate the view child to the given (left, top) position. If this method returns true, the caller - * should invoke {@link #continueSettling(boolean)} on each subsequent frame to continue the motion until it returns - * false. If this method returns false there is no further work to do to complete the movement. - * - *

- * This operation does not count as a capture event, though {@link #getCapturedView()} will still report the sliding - * view while the slide is in progress. - *

- * - * @param child - * Child view to capture and animate - * @param finalLeft - * Final left position of child - * @param finalTop - * Final top position of child - * @return true if animation should continue through {@link #continueSettling(boolean)} calls - */ - public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) { - mCapturedView = child; - mActivePointerId = INVALID_POINTER; - - boolean continueSliding = forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0); - if (!continueSliding && mDragState == STATE_IDLE && mCapturedView != null) { - // If we're in an IDLE state to begin with and aren't moving anywhere, we - // end up having a non-null capturedView with an IDLE dragState - mCapturedView = null; - } - - return continueSliding; - } - - /** - * Settle the captured view at the given (left, top) position. The appropriate velocity from prior motion will be - * taken into account. If this method returns true, the caller should invoke {@link #continueSettling(boolean)} on - * each subsequent frame to continue the motion until it returns false. If this method returns false there is no - * further work to do to complete the movement. - * - * @param finalLeft - * Settled left edge position for the captured view - * @param finalTop - * Settled top edge position for the captured view - * @return true if animation should continue through {@link #continueSettling(boolean)} calls - */ - public boolean settleCapturedViewAt(int finalLeft, int finalTop) { - if (!mReleaseInProgress) { - throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to " + "Callback#onViewReleased"); - } - - return forceSettleCapturedViewAt(finalLeft, finalTop, (int) mVelocityTracker.getXVelocity(mActivePointerId), - (int) mVelocityTracker.getYVelocity(mActivePointerId)); - } - - /** - * Settle the captured view at the given (left, top) position. - * - * @param finalLeft - * Target left position for the captured view - * @param finalTop - * Target top position for the captured view - * @param xvel - * Horizontal velocity - * @param yvel - * Vertical velocity - * @return true if animation should continue through {@link #continueSettling(boolean)} calls - */ - private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) { - final int startLeft = mCapturedView.getLeft(); - final int startTop = mCapturedView.getTop(); - final int dx = finalLeft - startLeft; - final int dy = finalTop - startTop; - - if (dx == 0 && dy == 0) { - // Nothing to do. Send callbacks, be done. - mScroller.abortAnimation(); - setDragState(STATE_IDLE); - return false; - } - - final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel); - mScroller.startScroll(startLeft, startTop, dx, dy, duration); - - setDragState(STATE_SETTLING); - return true; - } - - private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) { - xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity); - yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity); - final int absDx = Math.abs(dx); - final int absDy = Math.abs(dy); - final int absXVel = Math.abs(xvel); - final int absYVel = Math.abs(yvel); - final int addedVel = absXVel + absYVel; - final int addedDistance = absDx + absDy; - - final float xweight = xvel != 0 ? (float) absXVel / addedVel : (float) absDx / addedDistance; - final float yweight = yvel != 0 ? (float) absYVel / addedVel : (float) absDy / addedDistance; - - int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child)); - int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child)); - - return (int) (xduration * xweight + yduration * yweight); - } - - private int computeAxisDuration(int delta, int velocity, int motionRange) { - if (delta == 0) { - return 0; - } - - final int width = mParentView.getWidth(); - final int halfWidth = width / 2; - final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width); - final float distance = halfWidth + halfWidth * distanceInfluenceForSnapDuration(distanceRatio); - - int duration; - velocity = Math.abs(velocity); - if (velocity > 0) { - duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); - } else { - final float range = (float) Math.abs(delta) / motionRange; - duration = (int) ((range + 1) * BASE_SETTLE_DURATION); - } - return Math.min(duration, MAX_SETTLE_DURATION); - } - - /** - * Clamp the magnitude of value for absMin and absMax. If the value is below the minimum, it will be clamped to - * zero. If the value is above the maximum, it will be clamped to the maximum. - * - * @param value - * Value to clamp - * @param absMin - * Absolute value of the minimum significant value to return - * @param absMax - * Absolute value of the maximum value to return - * @return The clamped value with the same sign as value - */ - private static int clampMag(int value, int absMin, int absMax) { - final int absValue = Math.abs(value); - if (absValue < absMin) return 0; - if (absValue > absMax) return value > 0 ? absMax : -absMax; - return value; - } - - /** - * Clamp the magnitude of value for absMin and absMax. If the value is below the minimum, it will be clamped to - * zero. If the value is above the maximum, it will be clamped to the maximum. - * - * @param value - * Value to clamp - * @param absMin - * Absolute value of the minimum significant value to return - * @param absMax - * Absolute value of the maximum value to return - * @return The clamped value with the same sign as value - */ - private static float clampMag(float value, float absMin, float absMax) { - final float absValue = Math.abs(value); - if (absValue < absMin) return 0; - if (absValue > absMax) return value > 0 ? absMax : -absMax; - return value; - } - - private static float distanceInfluenceForSnapDuration(float f) { - f -= 0.5f; // center the values about 0. - f *= 0.3f * Math.PI / 2.0f; - return (float) Math.sin(f); - } - - /** - * Settle the captured view based on standard free-moving fling behavior. The caller should invoke - * {@link #continueSettling(boolean)} on each subsequent frame to continue the motion until it returns false. - * - * @param minLeft - * Minimum X position for the view's left edge - * @param minTop - * Minimum Y position for the view's top edge - * @param maxLeft - * Maximum X position for the view's left edge - * @param maxTop - * Maximum Y position for the view's top edge - */ - public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) { - if (!mReleaseInProgress) { - throw new IllegalStateException("Cannot flingCapturedView outside of a call to " + "Callback#onViewReleased"); - } - - mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(), (int) mVelocityTracker.getXVelocity(mActivePointerId), - (int) mVelocityTracker.getYVelocity(mActivePointerId), minLeft, maxLeft, minTop, maxTop); - - setDragState(STATE_SETTLING); - } - - /** - * Move the captured settling view by the appropriate amount for the current time. If continueSettling - * returns true, the caller should call it again on the next frame to continue. - * - * @param deferCallbacks - * true if state callbacks should be deferred via posted message. Set this to true if you are calling - * this method from {@link android.view.View#computeScroll()} or similar methods invoked as part of - * layout or drawing. - * @return true if settle is still in progress - */ - public boolean continueSettling(boolean deferCallbacks) { - if (mDragState == STATE_SETTLING) { - boolean keepGoing = mScroller.computeScrollOffset(); - final int x = mScroller.getCurrX(); - final int y = mScroller.getCurrY(); - final int dx = x - mCapturedView.getLeft(); - final int dy = y - mCapturedView.getTop(); - - if (dx != 0) { - mCapturedView.offsetLeftAndRight(dx); - } - if (dy != 0) { - mCapturedView.offsetTopAndBottom(dy); - } - - if (dx != 0 || dy != 0) { - mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy); - } - - if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) { - // Close enough. The interpolator/scroller might think we're still moving - // but the user sure doesn't. - mScroller.abortAnimation(); - keepGoing = false; - } - - if (!keepGoing) { - if (deferCallbacks) { - mParentView.post(mSetIdleRunnable); - } else { - setDragState(STATE_IDLE); - } - } - } - - return mDragState == STATE_SETTLING; - } - - /** - * Like all callback events this must happen on the UI thread, but release involves some extra semantics. During a - * release (mReleaseInProgress) is the only time it is valid to call {@link #settleCapturedViewAt(int, int)} or - * {@link #flingCapturedView(int, int, int, int)}. - */ - private void dispatchViewReleased(float xvel, float yvel) { - mReleaseInProgress = true; - mCallback.onViewReleased(mCapturedView, xvel, yvel); - mReleaseInProgress = false; - - if (mDragState == STATE_DRAGGING) { - // onViewReleased didn't call a method that would have changed this. Go idle. - setDragState(STATE_IDLE); - } - } - - private void clearMotionHistory() { - if (mInitialMotionX == null) { - return; - } - Arrays.fill(mInitialMotionX, 0); - Arrays.fill(mInitialMotionY, 0); - Arrays.fill(mLastMotionX, 0); - Arrays.fill(mLastMotionY, 0); - Arrays.fill(mInitialEdgesTouched, 0); - Arrays.fill(mEdgeDragsInProgress, 0); - Arrays.fill(mEdgeDragsLocked, 0); - mPointersDown = 0; - } - - private void clearMotionHistory(int pointerId) { - if (mInitialMotionX == null) { - return; - } - mInitialMotionX[pointerId] = 0; - mInitialMotionY[pointerId] = 0; - mLastMotionX[pointerId] = 0; - mLastMotionY[pointerId] = 0; - mInitialEdgesTouched[pointerId] = 0; - mEdgeDragsInProgress[pointerId] = 0; - mEdgeDragsLocked[pointerId] = 0; - mPointersDown &= ~(1 << pointerId); - } - - private void ensureMotionHistorySizeForId(int pointerId) { - if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) { - float[] imx = new float[pointerId + 1]; - float[] imy = new float[pointerId + 1]; - float[] lmx = new float[pointerId + 1]; - float[] lmy = new float[pointerId + 1]; - int[] iit = new int[pointerId + 1]; - int[] edip = new int[pointerId + 1]; - int[] edl = new int[pointerId + 1]; - - if (mInitialMotionX != null) { - System.arraycopy(mInitialMotionX, 0, imx, 0, mInitialMotionX.length); - System.arraycopy(mInitialMotionY, 0, imy, 0, mInitialMotionY.length); - System.arraycopy(mLastMotionX, 0, lmx, 0, mLastMotionX.length); - System.arraycopy(mLastMotionY, 0, lmy, 0, mLastMotionY.length); - System.arraycopy(mInitialEdgesTouched, 0, iit, 0, mInitialEdgesTouched.length); - System.arraycopy(mEdgeDragsInProgress, 0, edip, 0, mEdgeDragsInProgress.length); - System.arraycopy(mEdgeDragsLocked, 0, edl, 0, mEdgeDragsLocked.length); - } - - mInitialMotionX = imx; - mInitialMotionY = imy; - mLastMotionX = lmx; - mLastMotionY = lmy; - mInitialEdgesTouched = iit; - mEdgeDragsInProgress = edip; - mEdgeDragsLocked = edl; - } - } - - private void saveInitialMotion(float x, float y, int pointerId) { - ensureMotionHistorySizeForId(pointerId); - mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x; - mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y; - mInitialEdgesTouched[pointerId] = getEdgesTouched((int) x, (int) y); - mPointersDown |= 1 << pointerId; - } - - private void saveLastMotion(MotionEvent ev) { - final int pointerCount = ev.getPointerCount(); - for (int i = 0; i < pointerCount; i++) { - final int pointerId = ev.getPointerId(i); - final float x = ev.getX(i); - final float y = ev.getY(i); - mLastMotionX[pointerId] = x; - mLastMotionY[pointerId] = y; - } - } - - /** - * Check if the given pointer ID represents a pointer that is currently down (to the best of the ViewDragHelper's - * knowledge). - * - *

- * The state used to report this information is populated by the methods - * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or - * {@link #processTouchEvent(android.view.MotionEvent)}. If one of these methods has not been called for all - * relevant MotionEvents to track, the information reported by this method may be stale or incorrect. - *

- * - * @param pointerId - * pointer ID to check; corresponds to IDs provided by MotionEvent - * @return true if the pointer with the given ID is still down - */ - public boolean isPointerDown(int pointerId) { - return (mPointersDown & 1 << pointerId) != 0; - } - - void setDragState(int state) { - mParentView.removeCallbacks(mSetIdleRunnable); - if (mDragState != state) { - mDragState = state; - mCallback.onViewDragStateChanged(state); - if (mDragState == STATE_IDLE) { - mCapturedView = null; - } - } - } - - /** - * Attempt to capture the view with the given pointer ID. The callback will be involved. This will put us into the - * "dragging" state. If we've already captured this view with this pointer this method will immediately return true - * without consulting the callback. - * - * @param toCapture - * View to capture - * @param pointerId - * Pointer to capture with - * @return true if capture was successful - */ - boolean tryCaptureViewForDrag(View toCapture, int pointerId) { - if (toCapture == mCapturedView && mActivePointerId == pointerId) { - // Already done! - return true; - } - if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) { - mActivePointerId = pointerId; - captureChildView(toCapture, pointerId); - return true; - } - return false; - } - - /** - * Tests scrollability within child views of v given a delta of dx. - * - * @param v - * View to test for horizontal scrollability - * @param checkV - * Whether the view v passed should itself be checked for scrollability (true), or just its children - * (false). - * @param dx - * Delta scrolled in pixels along the X axis - * @param dy - * Delta scrolled in pixels along the Y axis - * @param x - * X coordinate of the active touch point - * @param y - * Y coordinate of the active touch point - * @return true if child views of v can be scrolled by delta of dx. - */ - protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) { - if (v instanceof ViewGroup) { - final ViewGroup group = (ViewGroup) v; - final int scrollX = v.getScrollX(); - final int scrollY = v.getScrollY(); - final int count = group.getChildCount(); - // Count backwards - let topmost views consume scroll distance first. - for (int i = count - 1; i >= 0; i--) { - // TODO: Add versioned support here for transformed views. - // This will not work for transformed views in Honeycomb+ - final View child = group.getChildAt(i); - if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && y + scrollY >= child.getTop() && y + scrollY < child.getBottom() - && canScroll(child, true, dx, dy, x + scrollX - child.getLeft(), y + scrollY - child.getTop())) { - return true; - } - } - } - - return checkV && (v.canScrollHorizontally(-dx) || v.canScrollVertically(-dy)); - } - - /** - * Check if this event as provided to the parent view's onInterceptTouchEvent should cause the parent to intercept - * the touch event stream. - * - * @param ev - * MotionEvent provided to onInterceptTouchEvent - * @return true if the parent view should return true from onInterceptTouchEvent - */ - public boolean shouldInterceptTouchEvent(MotionEvent ev) { - final int action = ev.getActionMasked(); - final int actionIndex = ev.getActionIndex(); - - if (action == MotionEvent.ACTION_DOWN) { - // Reset things for a new event stream, just in case we didn't get - // the whole previous stream. - cancel(); - } - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - - switch (action) { - case MotionEvent.ACTION_DOWN: { - final float x = ev.getX(); - final float y = ev.getY(); - final int pointerId = ev.getPointerId(0); - saveInitialMotion(x, y, pointerId); - - final View toCapture = findTopChildUnder((int) x, (int) y); - - // Catch a settling view if possible. - if (toCapture == mCapturedView && mDragState == STATE_SETTLING) { - tryCaptureViewForDrag(toCapture, pointerId); - } - - final int edgesTouched = mInitialEdgesTouched[pointerId]; - if ((edgesTouched & mTrackingEdges) != 0) { - mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); - } - break; - } - - case MotionEvent.ACTION_POINTER_DOWN: { - final int pointerId = ev.getPointerId(actionIndex); - final float x = ev.getX(actionIndex); - final float y = ev.getY(actionIndex); - - saveInitialMotion(x, y, pointerId); - - // A ViewDragHelper can only manipulate one view at a time. - if (mDragState == STATE_IDLE) { - final int edgesTouched = mInitialEdgesTouched[pointerId]; - if ((edgesTouched & mTrackingEdges) != 0) { - mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); - } - } else if (mDragState == STATE_SETTLING) { - // Catch a settling view if possible. - final View toCapture = findTopChildUnder((int) x, (int) y); - if (toCapture == mCapturedView) { - tryCaptureViewForDrag(toCapture, pointerId); - } - } - break; - } - - case MotionEvent.ACTION_MOVE: { - // First to cross a touch slop over a draggable view wins. Also report edge drags. - final int pointerCount = ev.getPointerCount(); - for (int i = 0; i < pointerCount; i++) { - final int pointerId = ev.getPointerId(i); - final float x = ev.getX(i); - final float y = ev.getY(i); - final float dx = x - mInitialMotionX[pointerId]; - final float dy = y - mInitialMotionY[pointerId]; - - final View toCapture = findTopChildUnder((int) x, (int) y); - final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy); - if (pastSlop) { - // check the callback's - // getView[Horizontal|Vertical]DragRange methods to know - // if you can move at all along an axis, then see if it - // would clamp to the same value. If you can't move at - // all in every dimension with a nonzero range, bail. - @SuppressWarnings("null") /* We only enter here if "toCapture != null" */ - final int oldLeft = toCapture.getLeft(); - final int targetLeft = oldLeft + (int) dx; - final int newLeft = mCallback.clampViewPositionHorizontal(toCapture, targetLeft, (int) dx); - final int oldTop = toCapture.getTop(); - final int targetTop = oldTop + (int) dy; - final int newTop = mCallback.clampViewPositionVertical(toCapture, targetTop, (int) dy); - final int horizontalDragRange = mCallback.getViewHorizontalDragRange(toCapture); - final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture); - if ((horizontalDragRange == 0 || horizontalDragRange > 0 && newLeft == oldLeft) - && (verticalDragRange == 0 || verticalDragRange > 0 && newTop == oldTop)) { - break; - } - } - reportNewEdgeDrags(dx, dy, pointerId); - if (mDragState == STATE_DRAGGING) { - // Callback might have started an edge drag - break; - } - - if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) { - break; - } - } - saveLastMotion(ev); - break; - } - - case MotionEvent.ACTION_POINTER_UP: { - final int pointerId = ev.getPointerId(actionIndex); - clearMotionHistory(pointerId); - break; - } - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: { - cancel(); - break; - } - } - - return mDragState == STATE_DRAGGING; - } - - /** - * Process a touch event received by the parent view. This method will dispatch callback events as needed before - * returning. The parent view's onTouchEvent implementation should call this. - * - * @param ev - * The touch event received by the parent view - */ - public void processTouchEvent(MotionEvent ev) { - final int action = ev.getActionMasked(); - final int actionIndex = ev.getActionIndex(); - - if (action == MotionEvent.ACTION_DOWN) { - // Reset things for a new event stream, just in case we didn't get - // the whole previous stream. - cancel(); - } - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - - switch (action) { - case MotionEvent.ACTION_DOWN: { - final float x = ev.getX(); - final float y = ev.getY(); - final int pointerId = ev.getPointerId(0); - final View toCapture = findTopChildUnder((int) x, (int) y); - - saveInitialMotion(x, y, pointerId); - - // Since the parent is already directly processing this touch event, - // there is no reason to delay for a slop before dragging. - // Start immediately if possible. - tryCaptureViewForDrag(toCapture, pointerId); - - final int edgesTouched = mInitialEdgesTouched[pointerId]; - if ((edgesTouched & mTrackingEdges) != 0) { - mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); - } - break; - } - - case MotionEvent.ACTION_POINTER_DOWN: { - final int pointerId = ev.getPointerId(actionIndex); - final float x = ev.getX(actionIndex); - final float y = ev.getY(actionIndex); - - saveInitialMotion(x, y, pointerId); - - // A ViewDragHelper can only manipulate one view at a time. - if (mDragState == STATE_IDLE) { - // If we're idle we can do anything! Treat it like a normal down event. - - final View toCapture = findTopChildUnder((int) x, (int) y); - tryCaptureViewForDrag(toCapture, pointerId); - - final int edgesTouched = mInitialEdgesTouched[pointerId]; - if ((edgesTouched & mTrackingEdges) != 0) { - mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); - } - } else if (isCapturedViewUnder((int) x, (int) y)) { - // We're still tracking a captured view. If the same view is under this - // point, we'll swap to controlling it with this pointer instead. - // (This will still work if we're "catching" a settling view.) - - tryCaptureViewForDrag(mCapturedView, pointerId); - } - break; - } - - case MotionEvent.ACTION_MOVE: { - if (mDragState == STATE_DRAGGING) { - final int index = ev.findPointerIndex(mActivePointerId); - final float x = ev.getX(index); - final float y = ev.getY(index); - final int idx = (int) (x - mLastMotionX[mActivePointerId]); - final int idy = (int) (y - mLastMotionY[mActivePointerId]); - - dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy); - - saveLastMotion(ev); - } else { - // Check to see if any pointer is now over a draggable view. - final int pointerCount = ev.getPointerCount(); - for (int i = 0; i < pointerCount; i++) { - final int pointerId = ev.getPointerId(i); - final float x = ev.getX(i); - final float y = ev.getY(i); - final float dx = x - mInitialMotionX[pointerId]; - final float dy = y - mInitialMotionY[pointerId]; - - reportNewEdgeDrags(dx, dy, pointerId); - if (mDragState == STATE_DRAGGING) { - // Callback might have started an edge drag. - break; - } - - final View toCapture = findTopChildUnder((int) x, (int) y); - if (checkTouchSlop(toCapture, dx, dy) && tryCaptureViewForDrag(toCapture, pointerId)) { - break; - } - } - saveLastMotion(ev); - } - break; - } - - case MotionEvent.ACTION_POINTER_UP: { - final int pointerId = ev.getPointerId(actionIndex); - if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) { - // Try to find another pointer that's still holding on to the captured view. - int newActivePointer = INVALID_POINTER; - final int pointerCount = ev.getPointerCount(); - for (int i = 0; i < pointerCount; i++) { - final int id = ev.getPointerId(i); - if (id == mActivePointerId) { - // This one's going away, skip. - continue; - } - - final float x = ev.getX(i); - final float y = ev.getY(i); - if (findTopChildUnder((int) x, (int) y) == mCapturedView && tryCaptureViewForDrag(mCapturedView, id)) { - newActivePointer = mActivePointerId; - break; - } - } - - if (newActivePointer == INVALID_POINTER) { - // We didn't find another pointer still touching the view, release it. - releaseViewForPointerUp(); - } - } - clearMotionHistory(pointerId); - break; - } - - case MotionEvent.ACTION_UP: { - if (mDragState == STATE_DRAGGING) { - releaseViewForPointerUp(); - } - cancel(); - break; - } - - case MotionEvent.ACTION_CANCEL: { - if (mDragState == STATE_DRAGGING) { - dispatchViewReleased(0, 0); - } - cancel(); - break; - } - } - } - - private void reportNewEdgeDrags(float dx, float dy, int pointerId) { - int dragsStarted = 0; - if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) { - dragsStarted |= EDGE_LEFT; - } - if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) { - dragsStarted |= EDGE_TOP; - } - if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) { - dragsStarted |= EDGE_RIGHT; - } - if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) { - dragsStarted |= EDGE_BOTTOM; - } - - if (dragsStarted != 0) { - mEdgeDragsInProgress[pointerId] |= dragsStarted; - mCallback.onEdgeDragStarted(dragsStarted, pointerId); - } - } - - private boolean checkNewEdgeDrag(float delta, float odelta, int pointerId, int edge) { - final float absDelta = Math.abs(delta); - final float absODelta = Math.abs(odelta); - - if ((mInitialEdgesTouched[pointerId] & edge) != edge || (mTrackingEdges & edge) == 0 || (mEdgeDragsLocked[pointerId] & edge) == edge - || (mEdgeDragsInProgress[pointerId] & edge) == edge || (absDelta <= mTouchSlop && absODelta <= mTouchSlop)) { - return false; - } - if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) { - mEdgeDragsLocked[pointerId] |= edge; - return false; - } - return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop; - } - - /** - * Check if we've crossed a reasonable touch slop for the given child view. If the child cannot be dragged along the - * horizontal or vertical axis, motion along that axis will not count toward the slop check. - * - * @param child - * Child to check - * @param dx - * Motion since initial position along X axis - * @param dy - * Motion since initial position along Y axis - * @return true if the touch slop has been crossed - */ - private boolean checkTouchSlop(View child, float dx, float dy) { - if (child == null) { - return false; - } - final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0; - final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0; - - if (checkHorizontal && checkVertical) { - return dx * dx + dy * dy > mTouchSlop * mTouchSlop; - } else if (checkHorizontal) { - return Math.abs(dx) > mTouchSlop; - } else if (checkVertical) { - return Math.abs(dy) > mTouchSlop; - } - return false; - } - - /** - * Check if any pointer tracked in the current gesture has crossed the required slop threshold. - * - *

- * This depends on internal state populated by {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or - * {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on the results of this method after - * all currently available touch data has been provided to one of these two methods. - *

- * - * @param directions - * Combination of direction flags, see {@link #DIRECTION_HORIZONTAL}, {@link #DIRECTION_VERTICAL}, - * {@link #DIRECTION_ALL} - * @return true if the slop threshold has been crossed, false otherwise - */ - public boolean checkTouchSlop(int directions) { - final int count = mInitialMotionX.length; - for (int i = 0; i < count; i++) { - if (checkTouchSlop(directions, i)) { - return true; - } - } - return false; - } - - /** - * Check if the specified pointer tracked in the current gesture has crossed the required slop threshold. - * - *

- * This depends on internal state populated by {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or - * {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on the results of this method after - * all currently available touch data has been provided to one of these two methods. - *

- * - * @param directions - * Combination of direction flags, see {@link #DIRECTION_HORIZONTAL}, {@link #DIRECTION_VERTICAL}, - * {@link #DIRECTION_ALL} - * @param pointerId - * ID of the pointer to slop check as specified by MotionEvent - * @return true if the slop threshold has been crossed, false otherwise - */ - public boolean checkTouchSlop(int directions, int pointerId) { - if (!isPointerDown(pointerId)) { - return false; - } - - final boolean checkHorizontal = (directions & DIRECTION_HORIZONTAL) == DIRECTION_HORIZONTAL; - final boolean checkVertical = (directions & DIRECTION_VERTICAL) == DIRECTION_VERTICAL; - - final float dx = mLastMotionX[pointerId] - mInitialMotionX[pointerId]; - final float dy = mLastMotionY[pointerId] - mInitialMotionY[pointerId]; - - if (checkHorizontal && checkVertical) { - return dx * dx + dy * dy > mTouchSlop * mTouchSlop; - } else if (checkHorizontal) { - return Math.abs(dx) > mTouchSlop; - } else if (checkVertical) { - return Math.abs(dy) > mTouchSlop; - } - return false; - } - - /** - * Check if any of the edges specified were initially touched in the currently active gesture. If there is no - * currently active gesture this method will return false. - * - * @param edges - * Edges to check for an initial edge touch. See {@link #EDGE_LEFT}, {@link #EDGE_TOP}, - * {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and {@link #EDGE_ALL} - * @return true if any of the edges specified were initially touched in the current gesture - */ - public boolean isEdgeTouched(int edges) { - final int count = mInitialEdgesTouched.length; - for (int i = 0; i < count; i++) { - if (isEdgeTouched(edges, i)) { - return true; - } - } - return false; - } - - /** - * Check if any of the edges specified were initially touched by the pointer with the specified ID. If there is no - * currently active gesture or if there is no pointer with the given ID currently down this method will return - * false. - * - * @param edges - * Edges to check for an initial edge touch. See {@link #EDGE_LEFT}, {@link #EDGE_TOP}, - * {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and {@link #EDGE_ALL} - * @return true if any of the edges specified were initially touched in the current gesture - */ - public boolean isEdgeTouched(int edges, int pointerId) { - return isPointerDown(pointerId) && (mInitialEdgesTouched[pointerId] & edges) != 0; - } - - private void releaseViewForPointerUp() { - mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); - final float xvel = clampMag(mVelocityTracker.getXVelocity(mActivePointerId), mMinVelocity, mMaxVelocity); - final float yvel = clampMag(mVelocityTracker.getYVelocity(mActivePointerId), mMinVelocity, mMaxVelocity); - dispatchViewReleased(xvel, yvel); - } - - private void dragTo(int left, int top, int dx, int dy) { - int clampedX = left; - int clampedY = top; - final int oldLeft = mCapturedView.getLeft(); - final int oldTop = mCapturedView.getTop(); - if (dx != 0) { - clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx); - mCapturedView.offsetLeftAndRight(clampedX - oldLeft); - } - if (dy != 0) { - clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy); - mCapturedView.offsetTopAndBottom(clampedY - oldTop); - } - - if (dx != 0 || dy != 0) { - final int clampedDx = clampedX - oldLeft; - final int clampedDy = clampedY - oldTop; - mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY, clampedDx, clampedDy); - } - } - - /** - * Determine if the currently captured view is under the given point in the parent view's coordinate system. If - * there is no captured view this method will return false. - * - * @param x - * X position to test in the parent's coordinate system - * @param y - * Y position to test in the parent's coordinate system - * @return true if the captured view is under the given point, false otherwise - */ - public boolean isCapturedViewUnder(int x, int y) { - return isViewUnder(mCapturedView, x, y); - } - - /** - * Determine if the supplied view is under the given point in the parent view's coordinate system. - * - * @param view - * Child view of the parent to hit test - * @param x - * X position to test in the parent's coordinate system - * @param y - * Y position to test in the parent's coordinate system - * @return true if the supplied view is under the given point, false otherwise - */ - public boolean isViewUnder(View view, int x, int y) { - return view != null && x >= view.getLeft() && x < view.getRight() && y >= view.getTop() && y < view.getBottom(); - } - - /** - * Find the topmost child under the given point within the parent view's coordinate system. The child order is - * determined using {@link Callback#getOrderedChildIndex(int)}. - * - * @param x - * X position to test in the parent's coordinate system - * @param y - * Y position to test in the parent's coordinate system - * @return The topmost child view under (x, y) or null if none found. - */ - public View findTopChildUnder(int x, int y) { - final int childCount = mParentView.getChildCount(); - for (int i = childCount - 1; i >= 0; i--) { - final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i)); - if (x >= child.getLeft() && x < child.getRight() && y >= child.getTop() && y < child.getBottom()) { - return child; - } - } - return null; - } - - private int getEdgesTouched(int x, int y) { - int result = 0; - - if (x < mParentView.getLeft() + mEdgeSize) result |= EDGE_LEFT; - if (y < mParentView.getTop() + mEdgeSize) result |= EDGE_TOP; - if (x > mParentView.getRight() - mEdgeSize) result |= EDGE_RIGHT; - if (y > mParentView.getBottom() - mEdgeSize) result |= EDGE_BOTTOM; - - return result; - } -} diff --git a/app/src/main/java/com/termux/drawer/package-info.java b/app/src/main/java/com/termux/drawer/package-info.java deleted file mode 100644 index 84bf92f7..00000000 --- a/app/src/main/java/com/termux/drawer/package-info.java +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Extraction (and some minor cleanup to get rid of warnings) of DrawerLayout from the - * Android Support Library. - * - * Source at: - * https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v4/java/android/support/v4/widget/DrawerLayout.java - * https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v4/java/android/support/v4/widget/ViewDragHelper.java - */ -package com.termux.drawer; - diff --git a/app/src/main/res/layout/drawer_layout.xml b/app/src/main/res/layout/drawer_layout.xml index 854da9de..219b028f 100644 --- a/app/src/main/res/layout/drawer_layout.xml +++ b/app/src/main/res/layout/drawer_layout.xml @@ -1,58 +1,73 @@ - + android:layout_height="match_parent" + android:orientation="vertical"> - + android:layout_alignParentTop="true" + android:layout_above="@+id/viewpager" + android:layout_height="match_parent"> - - - + android:layout_height="match_parent" + android:focusableInTouchMode="true" + android:scrollbarThumbVertical="@drawable/terminal_scroll_shape" + android:scrollbars="vertical" /> + android:id="@+id/left_drawer" + android:layout_width="240dp" + android:layout_height="match_parent" + android:layout_gravity="start" + android:background="@android:color/white" + android:choiceMode="singleChoice" + android:divider="@android:color/transparent" + android:dividerHeight="0dp" + android:orientation="vertical"> -