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
- * 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
- *
- * 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.
- *
- * This operation does not count as a capture event, though {@link #getCapturedView()} will still report the sliding
- * view while the slide is in progress.
- *
- * 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.
- *
- * 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.
- *
- * 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.
- * 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.
- *
- * onViewReleased
returns, the view will stop in place and the ViewDragHelper will return to
- * {@link #STATE_IDLE}.
- * 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.
- *
- * 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.
- *
- * 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).
- *
- *