summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMichael Kolb <kolby@google.com>2012-10-03 09:16:43 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2012-10-03 09:16:43 -0700
commit876aade725d376815eb227a4fa0a0295fe33a5e8 (patch)
treec0a5bcd8cd29fc78302739334b23eac658444bc8 /src
parentd202574157ef3f9e655bbadc6fe87a5638ecb690 (diff)
parenta5e23d9eb2bc96e7da4c021df2e11c1c43d379cd (diff)
downloadCamera-876aade725d376815eb227a4fa0a0295fe33a5e8.tar.gz
Merge "Completely redo UI" into gb-ub-photos-arches
Diffstat (limited to 'src')
-rw-r--r--src/com/android/camera/CameraActivity.java196
-rw-r--r--src/com/android/camera/PhotoModule.java20
-rw-r--r--src/com/android/camera/VideoModule.java11
-rw-r--r--src/com/android/camera/ui/CameraSwitcher.java285
-rw-r--r--src/com/android/camera/ui/PieRenderer.java6
-rw-r--r--src/com/android/camera/ui/ScrollerView.java1855
6 files changed, 168 insertions, 2205 deletions
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index af0fed07..8165de4a 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -50,10 +50,13 @@ public class CameraActivity extends ActivityBase
private FrameLayout mFrame;
private ShutterButton mShutter;
private RotateImageView mShutterIcon;
+ private RotateImageView mMenu;
private CameraSwitcher mSwitcher;
+ private View mShutterSwitcher;
private Drawable[] mDrawables;
- private int mSelectedModule;
- private Dispatcher mDispatcher;
+ private int mCurrentModuleIndex;
+ private MenuListener mMenuListener;
+ private MotionEvent mDown;
private MyOrientationEventListener mOrientationListener;
// The degrees of the device rotated clockwise from its natural orientation.
@@ -71,10 +74,13 @@ public class CameraActivity extends ActivityBase
R.drawable.ic_switch_photo_pan_holo_light
};
+ public interface MenuListener {
+ public void onMenuClicked();
+ }
+
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
- mDispatcher = new Dispatcher();
setContentView(R.layout.camera_main);
mFrame =(FrameLayout) findViewById(R.id.main_content);
mDrawables = new Drawable[DRAW_IDS.length];
@@ -86,38 +92,48 @@ public class CameraActivity extends ActivityBase
if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
|| MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
mCurrentModule = new VideoModule();
- mSelectedModule = VIDEO_MODULE_INDEX;
+ mCurrentModuleIndex = VIDEO_MODULE_INDEX;
} else {
mCurrentModule = new PhotoModule();
- mSelectedModule = PHOTO_MODULE_INDEX;
+ mCurrentModuleIndex = PHOTO_MODULE_INDEX;
}
mCurrentModule.init(this, mFrame, true);
- mSwitcher.setModuleIndex(mSelectedModule);
+ mSwitcher.setCurrentIndex(mCurrentModuleIndex);
mOrientationListener = new MyOrientationEventListener(this);
}
public void init() {
+ mShutterSwitcher = findViewById(R.id.camera_shutter_switcher);
mShutter = (ShutterButton) findViewById(R.id.shutter_button);
mShutterIcon = (RotateImageView) findViewById(R.id.shutter_overlay);
mShutterIcon.enableFilter(false);
+ mMenu = (RotateImageView) findViewById(R.id.menu);
mSwitcher = (CameraSwitcher) findViewById(R.id.camera_switcher);
+ mSwitcher.setDrawIds(DRAW_IDS);
+ mMenu.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mMenuListener != null) {
+ mMenuListener.onMenuClicked();
+ }
+ }
+ });
+ int[] drawids = new int[LightCycleHelper.hasLightCycleCapture(this)
+ ? DRAW_IDS.length : DRAW_IDS.length - 1];
+ int ix = 0;
for (int i = 0; i < mDrawables.length; i++) {
if (i == LIGHTCYCLE_MODULE_INDEX && !LightCycleHelper.hasLightCycleCapture(this)) {
continue; // not enabled, so don't add to UI
}
- RotateImageView iv = new RotateImageView(this);
- iv.setScaleType(ImageView.ScaleType.CENTER);
- iv.setImageDrawable(mDrawables[i]);
- iv.enableFilter(false);
- mSwitcher.add(iv, new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT));
- final int index = i;
- iv.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- mSwitcher.animateToModule(index);
- }
- });
+ drawids[ix++] = DRAW_IDS[i];
+ }
+ mSwitcher.setDrawIds(drawids);
+ }
+
+ public void setMenuListener(MenuListener listener) {
+ mMenuListener = listener;
+ if (mMenu != null) {
+ mMenu.setVisibility((listener != null) ? View.VISIBLE : View.GONE);
}
}
@@ -141,24 +157,25 @@ public class CameraActivity extends ActivityBase
// Rotate camera mode icons in the switcher
if (mOrientationCompensation != orientationCompensation) {
mOrientationCompensation = orientationCompensation;
- if(mSwitcher != null) {
- mSwitcher.rotateIcons(mOrientationCompensation, true);
+ if (mSwitcher != null) {
+ mSwitcher.setOrientation(mOrientationCompensation, true);
+ }
+ if (mMenu != null) {
+ mMenu.setOrientation(mOrientationCompensation, true);
}
}
mCurrentModule.onOrientationChanged(orientation);
}
}
- @Override
- public void onScroll() {
- }
@Override
public void onCameraSelected(int i) {
- if (i != mSelectedModule) {
+ if (i != mCurrentModuleIndex) {
mPaused = true;
boolean wasPanorama = isPanoramaActivity();
closeModule(mCurrentModule);
- mSelectedModule = i;
+ mCurrentModuleIndex = i;
+ mMenuListener = null;
switch (i) {
case VIDEO_MODULE_INDEX:
mCurrentModule = new VideoModule();
@@ -192,7 +209,7 @@ public class CameraActivity extends ActivityBase
}
private void showShutterIcon(boolean show) {
- showShutterIcon(show, DRAW_IDS[mSelectedModule]);
+ showShutterIcon(show, DRAW_IDS[mCurrentModuleIndex]);
}
private void showShutterIcon(boolean show, int resid) {
@@ -221,17 +238,22 @@ public class CameraActivity extends ActivityBase
// hide the switcher and show the given shutter icon
public void hideSwitcher(int resid) {
mSwitcher.setVisibility(View.GONE);
+ mMenu.setVisibility(View.GONE);
showShutterIcon(true, resid);
}
public void hideSwitcher() {
mSwitcher.setVisibility(View.GONE);
+ mMenu.setVisibility(View.GONE);
showShutterIcon(true);
}
public void showSwitcher() {
if (mCurrentModule.needsSwitcher()) {
mSwitcher.setVisibility(View.VISIBLE);
+ if (mMenuListener != null) {
+ mMenu.setVisibility(View.VISIBLE);
+ }
showShutterIcon(false);
}
}
@@ -250,7 +272,7 @@ public class CameraActivity extends ActivityBase
inflater.inflate(R.layout.camera_shutter_switcher, appRoot);
init();
- mSwitcher.animateToModule(mSelectedModule);
+ mSwitcher.setImageResource(DRAW_IDS[mCurrentModuleIndex]);
mSwitcher.setSwitchListener(this);
if (mShowCameraAppView) {
showUI();
@@ -340,23 +362,24 @@ public class CameraActivity extends ActivityBase
}
public void cancelActivityTouchHandling() {
- MotionEvent e = mDispatcher.getCancelEvent();
- if (e != null) {
- super.dispatchTouchEvent(e);
+ if (mDown != null) {
+ MotionEvent cancel = MotionEvent.obtain(mDown);
+ cancel.setAction(MotionEvent.ACTION_CANCEL);
+ super.dispatchTouchEvent(cancel);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent m) {
- boolean handled = false;
- if ((m.getActionMasked() == MotionEvent.ACTION_DOWN) || mDispatcher.isActive()) {
- mShutter.enableTouch(true);
- mSwitcher.enableTouch(true);
- handled = mDispatcher.dispatchTouchEvent(m);
- mShutter.enableTouch(false);
- mSwitcher.enableTouch(false);
+ if (m.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mDown = m;
+ }
+ if ((mSwitcher != null) && mSwitcher.showsPopup() && !mSwitcher.isInsidePopup(m)) {
+ return mSwitcher.onTouch(null, m);
+ } else {
+ return mShutterSwitcher.dispatchTouchEvent(m)
+ || mCurrentModule.dispatchTouchEvent(m);
}
- return handled || mCurrentModule.dispatchTouchEvent(m);
}
public boolean superDispatchTouchEvent(MotionEvent m) {
@@ -391,99 +414,4 @@ public class CameraActivity extends ActivityBase
public boolean isPanoramaActivity() {
return (mCurrentModule instanceof PanoramaModule);
}
-
- private class Dispatcher {
-
- private boolean mActive;
- private MotionEvent mDown;
- private int mSlop;
- private boolean mDownInShutter;
-
- public Dispatcher() {
- mSlop = ViewConfiguration.get(CameraActivity.this).getScaledTouchSlop();
- mActive = true;
- }
-
- public MotionEvent getCancelEvent() {
- if (mDown != null) {
- MotionEvent cancel = MotionEvent.obtain(mDown);
- cancel.setAction(MotionEvent.ACTION_CANCEL);
- return cancel;
- }
- return null;
- }
-
- public boolean isActive() {
- return mActive;
- }
-
- public boolean dispatchTouchEvent(MotionEvent m) {
- switch (m.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mActive = false;
- mDownInShutter = isInside(m, mShutter);
- mDown = m;
- if (mDownInShutter) {
- sendTo(m, mShutter);
- mActive = true;
- }
- if (isInside(m, mSwitcher)) {
- sendTo(m, mSwitcher);
- mActive = true;
- }
- break;
- case MotionEvent.ACTION_MOVE:
- if (mDownInShutter) {
- if (Math.abs(m.getX() - mDown.getX()) > mSlop) {
- // sliding switcher
- mDownInShutter = false;
- MotionEvent cancel = MotionEvent.obtain(m);
- cancel.setAction(MotionEvent.ACTION_CANCEL);
- sendTo(cancel, mShutter);
- sendTo(m, mSwitcher);
- } else {
- sendTo(m, mShutter);
- sendTo(m, mSwitcher);
- }
- } else {
- sendTo(m, mSwitcher);
- }
- break;
- case MotionEvent.ACTION_UP:
- if (mDownInShutter) {
- sendTo(m, mShutter);
- MotionEvent cancel = MotionEvent.obtain(m);
- cancel.setAction(MotionEvent.ACTION_CANCEL);
- sendTo(cancel, mSwitcher);
- } else {
- sendTo(m, mSwitcher);
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- if (mDownInShutter) {
- sendTo(m, mShutter);
- }
- sendTo(m, mSwitcher);
- break;
- }
- return mActive;
- }
- }
-
- private boolean sendTo(MotionEvent m, View v) {
- return v.dispatchTouchEvent(transformEvent(m, v));
- }
-
- private boolean isInside(MotionEvent evt, View v) {
- return (v.getVisibility() == View.VISIBLE
- && evt.getX() >= v.getLeft() && evt.getX() < v.getRight()
- && evt.getY() >= v.getTop() && evt.getY() < v.getBottom());
- }
-
- private MotionEvent transformEvent(MotionEvent m, View v) {
- MotionEvent r = MotionEvent.obtain(m);
- r.offsetLocation(- v.getLeft(), - v.getTop());
- return r;
- }
-
}
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 5b4a7588..66b8989e 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -19,13 +19,10 @@ package com.android.camera;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
-import android.content.BroadcastReceiver;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
-import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.SharedPreferences.Editor;
import android.content.res.Configuration;
import android.graphics.Bitmap;
@@ -72,7 +69,6 @@ import com.android.camera.ui.PopupManager;
import com.android.camera.ui.PreviewSurfaceView;
import com.android.camera.ui.RenderOverlay;
import com.android.camera.ui.Rotatable;
-import com.android.camera.ui.RotateImageView;
import com.android.camera.ui.RotateLayout;
import com.android.camera.ui.RotateTextToast;
import com.android.camera.ui.TwoStateImageView;
@@ -98,7 +94,8 @@ public class PhotoModule
PreviewFrameLayout.OnSizeChangedListener,
ShutterButton.OnShutterButtonListener,
SurfaceHolder.Callback,
- PieRenderer.PieListener {
+ PieRenderer.PieListener,
+ CameraActivity.MenuListener {
private static final String TAG = "CAM_PhotoModule";
@@ -459,6 +456,7 @@ public class PhotoModule
mCameraStartUpThread.start();
mActivity.getLayoutInflater().inflate(R.layout.photo_module, (ViewGroup) mRootView);
+ mActivity.setMenuListener(this);
// Surface texture is from camera screen nail and startPreview needs it.
// This must be done before startPreview.
@@ -555,6 +553,11 @@ public class PhotoModule
}
private void initializeAfterCameraOpen() {
+ if (mRenderOverlay != null) {
+ // render overlay is not initialized when setOrientationIndicator is called
+ // the first time, so do it here in UI thread
+ mRenderOverlay.setOrientation(mOrientationCompensation, false);
+ }
if (mPieRenderer == null) {
mPieRenderer = new PieRenderer(mActivity);
mPhotoControl = new PhotoController(mActivity, this, mPieRenderer);
@@ -1915,6 +1918,13 @@ public class PhotoModule
setCameraParameters(UPDATE_PARAM_PREFERENCE);
}
+ @Override
+ public void onMenuClicked() {
+ if (mPieRenderer != null) {
+ mPieRenderer.showInCenter();
+ }
+ }
+
// Preview area is touched. Handle touch focus.
@Override
public void onSingleTapUp(View view, int x, int y) {
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index 4c288682..57452f2b 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -89,7 +89,8 @@ public class VideoModule implements CameraModule,
MediaRecorder.OnErrorListener,
MediaRecorder.OnInfoListener,
EffectsRecorder.EffectsListener,
- PieRenderer.PieListener {
+ PieRenderer.PieListener,
+ CameraActivity.MenuListener {
private static final String TAG = "CAM_VideoModule";
@@ -438,6 +439,7 @@ public class VideoModule implements CameraModule,
mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView);
+ mActivity.setMenuListener(this);
// Surface texture is from camera screen nail and startPreview needs it.
// This must be done before startPreview.
mIsVideoCaptureIntent = isVideoCaptureIntent();
@@ -1095,6 +1097,13 @@ public class VideoModule implements CameraModule,
}
@Override
+ public void onMenuClicked() {
+ if (mPieRenderer != null) {
+ mPieRenderer.showFade();
+ }
+ }
+
+ @Override
public boolean onBackPressed() {
if (mPaused) return true;
if (mMediaRecorderRecording) {
diff --git a/src/com/android/camera/ui/CameraSwitcher.java b/src/com/android/camera/ui/CameraSwitcher.java
index 338b926c..ecc9f87e 100644
--- a/src/com/android/camera/ui/CameraSwitcher.java
+++ b/src/com/android/camera/ui/CameraSwitcher.java
@@ -17,274 +17,139 @@
package com.android.camera.ui;
import android.content.Context;
-import android.content.res.Configuration;
-import android.os.Handler;
-import android.os.Message;
import android.util.AttributeSet;
-import android.util.Log;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.LinearLayout;
-public class CameraSwitcher extends ScrollerView {
+import com.android.camera.R;
- private static final String TAG = "CAM_Switcher";
-
- private static final long FLING_DURATION = 300;
+public class CameraSwitcher extends RotateImageView
+ implements OnClickListener, OnTouchListener {
- private static final int MSG_SNAP = 1;
- private static final int MSG_SET_CAM = 2;
+ private static final String TAG = "CAM_Switcher";
public interface CameraSwitchListener {
- public void onScroll();
-
public void onCameraSelected(int i);
}
- private LinearLayout mContent;
private CameraSwitchListener mListener;
- private int mCurrentModule;
- private int mChildSize;
- private int mOffset;
- private boolean mHorizontal = true;
- private boolean mTouchEnabled;
-
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_SNAP:
- snap();
- break;
- case MSG_SET_CAM:
- if (mListener != null) {
- mListener.onCameraSelected(mCurrentModule);
- }
- break;
- }
- }
- };
+ private int mCurrentIndex;
+ private int[] mDrawIds;
+ private int mItemSize;
+ private View mPopup;
+ private View mParent;
public CameraSwitcher(Context context) {
super(context);
init(context);
}
- public CameraSwitcher(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init(context);
- }
-
public CameraSwitcher(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
- setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
- setFillViewport(true);
- setHorizontalScrollBarEnabled(false);
- setVerticalScrollBarEnabled(false);
- mContent = new LinearLayout(context);
- addView(mContent, new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.WRAP_CONTENT));
- updateOrientation();
- // initialize to non-zero
- mChildSize = 100;
- mOffset = 100;
+ mItemSize = context.getResources().getDimensionPixelSize(R.dimen.switcher_size);
+ setOnClickListener(this);
}
- public void setModuleIndex(int index) {
- mCurrentModule = index;
- enable(0, mCurrentModule);
- setIgnoreScroll(true);
- if (mHorizontal) {
- scrollTo(convertModuleView(mCurrentModule) * mChildSize, 0);
- } else {
- scrollTo(0, convertModuleView(mCurrentModule) * mChildSize);
- }
- setIgnoreScroll(false);
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent m) {
- if (mTouchEnabled) {
- return super.dispatchTouchEvent(m);
- } else {
- return false;
- }
- }
-
- public void enableTouch(boolean enable) {
- mTouchEnabled = enable;
- }
-
- private void updateOrientation() {
- Configuration config = getContext().getResources().getConfiguration();
- switch(config.orientation) {
- case Configuration.ORIENTATION_LANDSCAPE:
- case Configuration.ORIENTATION_UNDEFINED:
- setOrientation(LinearLayout.VERTICAL);
- break;
- case Configuration.ORIENTATION_PORTRAIT:
- setOrientation(LinearLayout.HORIZONTAL);
- break;
- }
- }
- public void setOrientation(int orientation) {
- mContent.setOrientation(orientation);
- if (orientation == LinearLayout.HORIZONTAL) {
- mHorizontal = true;
- mContent.setLayoutParams(
- new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
- } else {
- mHorizontal = false;
- mContent.setLayoutParams(
- new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
- }
- super.setOrientation(orientation);
+ public void setDrawIds(int[] drawids) {
+ mDrawIds = drawids;
}
- public void animateToModule(int i) {
- int oldModule = mCurrentModule;
- mCurrentModule = i;
- enable(oldModule, mCurrentModule);
- reposition();
- // TODO: Replace this with a proper animation instead of a smoothScrollTo
- // and a magic number
- mHandler.sendEmptyMessageDelayed(MSG_SET_CAM, 170);
+ public void setCurrentIndex(int i) {
+ mCurrentIndex = i;
+ setImageResource(mDrawIds[i]);
}
public void setSwitchListener(CameraSwitchListener l) {
mListener = l;
}
- private int clampModule(int index) {
- index = Math.max(0, index);
- return Math.min(index, mContent.getChildCount() - 1);
+ @Override
+ public void onClick(View v) {
+ showSwitcher();
}
- @Override
- public void fling(int velocity) {
- if (velocity == 0) {
- snap();
- } else {
- int newModule = clampModule(mCurrentModule + (velocity > 0 ? 1 : -1));
- animateToModule(newModule);
+ private void onCameraSelected(int ix) {
+ hidePopup();
+ if ((ix != mCurrentIndex) && (mListener != null)) {
+ setCurrentIndex(ix);
+ mListener.onCameraSelected(ix);
}
}
- @Override
- public void onScrollChanged(int sx, int sy, int oldx, int oldy) {
- if (!ignoreScroll()) {
- if (mListener != null) {
- mListener.onScroll();
- }
+ private void initPopup() {
+ mParent = LayoutInflater.from(getContext()).inflate(R.layout.switcher_popup,
+ (ViewGroup) getParent());
+ LinearLayout content = (LinearLayout) mParent.findViewById(R.id.content);
+ mPopup = content;
+ mPopup.setVisibility(View.GONE);
+ for (int i = 0; i < mDrawIds.length; i++) {
+ RotateImageView item = new RotateImageView(getContext());
+ item.setImageResource(mDrawIds[i]);
+ final int index = i;
+ item.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onCameraSelected(index);
+ }
+ });
+ content.addView(item, new LinearLayout.LayoutParams(mItemSize, mItemSize));
}
}
- public void add(View view, ViewGroup.LayoutParams lp) {
- mContent.addView(view, lp);
+ public boolean showsPopup() {
+ return (mPopup != null) && (mPopup.getVisibility() == View.VISIBLE);
}
- public void rotateIcons(int degree, boolean animation)
- {
- for(int i = 0; i < mContent.getChildCount(); i++) {
- View v = mContent.getChildAt(i);
- if (v instanceof RotateImageView) {
- ((RotateImageView) v).setOrientation(degree, animation);
- }
- }
+ public boolean isInsidePopup(MotionEvent evt) {
+ if (mPopup == null) return false;
+ return (mPopup.getVisibility() == View.VISIBLE && evt.getX() >= mPopup.getLeft()
+ && evt.getX() < mPopup.getRight() && evt.getY() >= mPopup.getTop()
+ && evt.getY() < mPopup.getBottom());
}
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- if (!changed) return;
- // child width
- int n = mContent.getChildCount();
- if (mHorizontal) {
- // layout left to right
- int w = r - l;
- mChildSize = w * 2 / 5;
- mOffset = (w - mChildSize) / 2;
- mContent.layout(0, 0, mChildSize * n + 2 * mOffset, (b - t));
- int cl = mOffset;
- int h = b - t;
- for (int i = 0; i < n; i++) {
- View c = mContent.getChildAt(i);
- c.layout(cl, 0, cl + mChildSize, h);
- cl += mChildSize;
- }
- } else {
- // layout bottom to top
- int h = b - t;
- mChildSize = h * 2 / 5;
- mOffset = (h - mChildSize) / 2;
- mContent.layout(0, 0, (r - l), mChildSize * n + 2 * mOffset);
- int cl = mOffset;
- int w = r - l;
- for (int i = n - 1; i >= 0; i--) {
- View c = mContent.getChildAt(i);
- c.layout(0, cl, w, cl + mChildSize);
- cl += mChildSize;
- }
+ private void hidePopup() {
+ if (mPopup != null) {
+ mPopup.setVisibility(View.GONE);
+ setVisibility(View.VISIBLE);
}
- reposition();
- }
-
- @Override
- protected void onScrollUp() {
- snap();
+ mParent.setOnTouchListener(null);
}
- @Override
- protected void onOrthoDragFinished(View v) {
- snap();
+ private void showSwitcher() {
+ if (mPopup == null) {
+ initPopup();
+ }
+ setVisibility(View.GONE);
+ mPopup.setVisibility(View.VISIBLE);
+ mParent.setOnTouchListener(this);
}
@Override
- protected void onOrthoFling(View v, float velocity) {
- snap();
- }
-
- private void reposition() {
- if (mHorizontal) {
- smoothScrollTo(convertModuleView(mCurrentModule) * mChildSize, 0);
- } else {
- smoothScrollTo(0, convertModuleView(mCurrentModule) * mChildSize);
+ public boolean onTouch(View v, MotionEvent event) {
+ if (showsPopup()) {
+ hidePopup();
}
+ return true;
}
- private void snap() {
- int cx = mHorizontal ? getScrollX() + getWidth() / 2 - mOffset :
- getScrollY() + getHeight() / 2 - mOffset ;
- if (mChildSize != 0) {
- final int centerpos = cx / mChildSize;
- if (mHorizontal) {
- smoothScrollTo(centerpos * mChildSize, 0);
- } else {
- smoothScrollTo(0, centerpos * mChildSize);
- }
- int oldModule = mCurrentModule;
- mCurrentModule = convertModuleView(centerpos);
- if (oldModule != mCurrentModule) {
- enable(oldModule, mCurrentModule);
- mHandler.sendEmptyMessageDelayed(MSG_SET_CAM, 100);
- }
+ @Override
+ public void setOrientation(int degree, boolean animate) {
+ super.setOrientation(degree, animate);
+ ViewGroup content = (ViewGroup) mPopup;
+ if (content == null) return;
+ for (int i = 0; i < content.getChildCount(); i++) {
+ RotateImageView iv = (RotateImageView) content.getChildAt(i);
+ iv.setOrientation(degree, animate);
}
}
-
- private void enable(int oldModule, int newModule) {
- mContent.getChildAt(convertModuleView(oldModule)).setEnabled(true);
- mContent.getChildAt(convertModuleView(newModule)).setEnabled(false);
- }
-
- /**
- * converts to/from module index from/to child index
- */
- private int convertModuleView(int moduleIndex) {
- return (mHorizontal ? moduleIndex : (mContent.getChildCount() - moduleIndex - 1));
- }
}
diff --git a/src/com/android/camera/ui/PieRenderer.java b/src/com/android/camera/ui/PieRenderer.java
index 83bc260a..3a0d1676 100644
--- a/src/com/android/camera/ui/PieRenderer.java
+++ b/src/com/android/camera/ui/PieRenderer.java
@@ -257,6 +257,12 @@ public class PieRenderer extends OverlayRenderer
setViewAlpha(mOverlay, 1);
}
+ public void showInCenter() {
+ setCenter(mCenterX, mCenterY);
+ mTapMode = true;
+ show(true);
+ }
+
/**
* guaranteed has center set
* @param show
diff --git a/src/com/android/camera/ui/ScrollerView.java b/src/com/android/camera/ui/ScrollerView.java
deleted file mode 100644
index ad109680..00000000
--- a/src/com/android/camera/ui/ScrollerView.java
+++ /dev/null
@@ -1,1855 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-package com.android.camera.ui;
-
-import android.content.Context;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.FocusFinder;
-import android.view.InputDevice;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.animation.AnimationUtils;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.OverScroller;
-import android.widget.TextView;
-
-import java.util.List;
-
-/**
- * Layout container for a view hierarchy that can be scrolled by the user,
- * allowing it to be larger than the physical display. A ScrollView
- * is a {@link FrameLayout}, meaning you should place one child in it
- * containing the entire contents to scroll; this child may itself be a layout
- * manager with a complex hierarchy of objects. A child that is often used
- * is a {@link LinearLayout} in a vertical orientation, presenting a vertical
- * array of top-level items that the user can scroll through.
- *
- * <p>The {@link TextView} class also
- * takes care of its own scrolling, so does not require a ScrollView, but
- * using the two together is possible to achieve the effect of a text view
- * within a larger container.
- *
- * <p>ScrollView only supports vertical scrolling.
- *
- * @attr ref android.R.styleable#ScrollView_fillViewport
- */
-public class ScrollerView extends FrameLayout {
- static final int ANIMATED_SCROLL_GAP = 250;
-
- static final float MAX_SCROLL_FACTOR = 0.5f;
-
- private long mLastScroll;
-
- private final Rect mTempRect = new Rect();
- protected OverScroller mScroller;
-
- /**
- * Position of the last motion event.
- */
- private float mLastMotionY;
-
- /**
- * True when the layout has changed but the traversal has not come through yet.
- * Ideally the view hierarchy would keep track of this for us.
- */
- private boolean mIsLayoutDirty = true;
-
- /**
- * The child to give focus to in the event that a child has requested focus while the
- * layout is dirty. This prevents the scroll from being wrong if the child has not been
- * laid out before requesting focus.
- */
- protected View mChildToScrollTo = null;
-
- /**
- * True if the user is currently dragging this ScrollView around. This is
- * not the same as 'is being flinged', which can be checked by
- * mScroller.isFinished() (flinging begins when the user lifts his finger).
- */
- protected boolean mIsBeingDragged = false;
-
- /**
- * Determines speed during touch scrolling
- */
- private VelocityTracker mVelocityTracker;
-
- /**
- * When set to true, the scroll view measure its child to make it fill the currently
- * visible area.
- */
- @ViewDebug.ExportedProperty(category = "layout")
- private boolean mFillViewport;
-
- /**
- * Whether arrow scrolling is animated.
- */
- private boolean mSmoothScrollingEnabled = true;
-
- private int mTouchSlop;
- protected int mMinimumVelocity;
- private int mMaximumVelocity;
-
- private int mOverscrollDistance;
- private int mOverflingDistance;
-
- /**
- * ID of the active pointer. This is used to retain consistency during
- * drags/flings if multiple pointers are used.
- */
- private int mActivePointerId = INVALID_POINTER;
-
-
- /**
- * Sentinel value for no current active pointer.
- * Used by {@link #mActivePointerId}.
- */
- private static final int INVALID_POINTER = -1;
-
- /**
- * orientation of the scrollview
- */
- protected boolean mHorizontal;
-
- protected boolean mIsOrthoDragged;
- private float mLastOrthoCoord;
- private View mDownView;
- private PointF mDownCoords;
-
- private float mScrollFactor;
- private boolean mIgnoreScroll;
-
- public ScrollerView(Context context) {
- this(context, null);
- }
-
- public ScrollerView(Context context, AttributeSet attrs) {
- this(context, attrs, android.R.attr.scrollViewStyle);
- }
-
- public ScrollerView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- initScrollView();
- }
-
- private void initScrollView() {
- mScroller = new OverScroller(getContext());
- setFocusable(true);
- setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
- setWillNotDraw(false);
- final ViewConfiguration configuration = ViewConfiguration.get(getContext());
- mTouchSlop = configuration.getScaledTouchSlop();
- mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
- mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
- mOverscrollDistance = configuration.getScaledOverscrollDistance();
- mOverflingDistance = configuration.getScaledOverflingDistance();
- mDownCoords = new PointF();
- mScrollFactor = 5;
- TypedValue outValue = new TypedValue();
- if (!getContext().getTheme().resolveAttribute(
- android.R.attr.listPreferredItemHeight, outValue, true)) {
- throw new IllegalStateException(
- "Expected theme to define listPreferredItemHeight.");
- }
- mScrollFactor = outValue.getDimension(
- getContext().getResources().getDisplayMetrics());
- }
-
- public void setOrientation(int orientation) {
- mHorizontal = (orientation == LinearLayout.HORIZONTAL);
- requestLayout();
- }
-
- public boolean ignoreScroll() {
- return mIgnoreScroll;
- }
-
- public void setIgnoreScroll(boolean ignore) {
- mIgnoreScroll = ignore;
- }
-
- @Override
- public boolean shouldDelayChildPressedState() {
- return true;
- }
-
- @Override
- protected float getTopFadingEdgeStrength() {
- if (getChildCount() == 0) {
- return 0.0f;
- }
- if (mHorizontal) {
- final int length = getHorizontalFadingEdgeLength();
- if (getScrollX() < length) {
- return getScrollX() / (float) length;
- }
- } else {
- final int length = getVerticalFadingEdgeLength();
- if (getScrollY() < length) {
- return getScrollY() / (float) length;
- }
- }
- return 1.0f;
- }
-
- @Override
- protected float getBottomFadingEdgeStrength() {
- if (getChildCount() == 0) {
- return 0.0f;
- }
- if (mHorizontal) {
- final int length = getHorizontalFadingEdgeLength();
- final int bottomEdge = getWidth() - getPaddingRight();
- final int span = getChildAt(0).getRight() - getScrollX() - bottomEdge;
- if (span < length) {
- return span / (float) length;
- }
- } else {
- final int length = getVerticalFadingEdgeLength();
- final int bottomEdge = getHeight() - getPaddingBottom();
- final int span = getChildAt(0).getBottom() - getScrollY() - bottomEdge;
- if (span < length) {
- return span / (float) length;
- }
- }
- return 1.0f;
- }
-
- /**
- * @return The maximum amount this scroll view will scroll in response to
- * an arrow event.
- */
- public int getMaxScrollAmount() {
- return (int) (MAX_SCROLL_FACTOR * (mHorizontal
- ? (getRight() - getLeft()) : (getBottom() - getTop())));
- }
-
-
- @Override
- public void addView(View child) {
- if (getChildCount() > 0) {
- throw new IllegalStateException("ScrollView can host only one direct child");
- }
-
- super.addView(child);
- }
-
- @Override
- public void addView(View child, int index) {
- if (getChildCount() > 0) {
- throw new IllegalStateException("ScrollView can host only one direct child");
- }
-
- super.addView(child, index);
- }
-
- @Override
- public void addView(View child, ViewGroup.LayoutParams params) {
- if (getChildCount() > 0) {
- throw new IllegalStateException("ScrollView can host only one direct child");
- }
-
- super.addView(child, params);
- }
-
- @Override
- public void addView(View child, int index, ViewGroup.LayoutParams params) {
- if (getChildCount() > 0) {
- throw new IllegalStateException("ScrollView can host only one direct child");
- }
-
- super.addView(child, index, params);
- }
-
- /**
- * @return Returns true this ScrollView can be scrolled
- */
- private boolean canScroll() {
- View child = getChildAt(0);
- if (child != null) {
- if (mHorizontal) {
- return getWidth() < child.getWidth() + getPaddingLeft() + getPaddingRight();
- } else {
- return getHeight() < child.getHeight() + getPaddingTop() + getPaddingBottom();
- }
- }
- return false;
- }
-
- /**
- * Indicates whether this ScrollView's content is stretched to fill the viewport.
- *
- * @return True if the content fills the viewport, false otherwise.
- *
- * @attr ref android.R.styleable#ScrollView_fillViewport
- */
- public boolean isFillViewport() {
- return mFillViewport;
- }
-
- /**
- * Indicates this ScrollView whether it should stretch its content height to fill
- * the viewport or not.
- *
- * @param fillViewport True to stretch the content's height to the viewport's
- * boundaries, false otherwise.
- *
- * @attr ref android.R.styleable#ScrollView_fillViewport
- */
- public void setFillViewport(boolean fillViewport) {
- if (fillViewport != mFillViewport) {
- mFillViewport = fillViewport;
- requestLayout();
- }
- }
-
- /**
- * @return Whether arrow scrolling will animate its transition.
- */
- public boolean isSmoothScrollingEnabled() {
- return mSmoothScrollingEnabled;
- }
-
- /**
- * Set whether arrow scrolling will animate its transition.
- * @param smoothScrollingEnabled whether arrow scrolling will animate its transition
- */
- public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
- mSmoothScrollingEnabled = smoothScrollingEnabled;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- if (!mFillViewport) {
- return;
- }
-
- final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- if (heightMode == MeasureSpec.UNSPECIFIED) {
- return;
- }
-
- if (getChildCount() > 0) {
- final View child = getChildAt(0);
- if (mHorizontal) {
- int width = getMeasuredWidth();
- if (child.getMeasuredWidth() < width) {
- final FrameLayout.LayoutParams lp = (LayoutParams) child
- .getLayoutParams();
-
- int childHeightMeasureSpec = getChildMeasureSpec(
- heightMeasureSpec, getPaddingTop() + getPaddingBottom(),
- lp.height);
- width -= getPaddingLeft();
- width -= getPaddingRight();
- int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
- width, MeasureSpec.EXACTLY);
-
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
- } else {
- int height = getMeasuredHeight();
- if (child.getMeasuredHeight() < height) {
- final FrameLayout.LayoutParams lp = (LayoutParams) child
- .getLayoutParams();
-
- int childWidthMeasureSpec = getChildMeasureSpec(
- widthMeasureSpec, getPaddingLeft() + getPaddingRight(),
- lp.width);
- height -= getPaddingTop();
- height -= getPaddingBottom();
- int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
- height, MeasureSpec.EXACTLY);
-
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
- }
- }
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- // Let the focused view and/or our descendants get the key first
- return super.dispatchKeyEvent(event) || executeKeyEvent(event);
- }
-
- /**
- * You can call this function yourself to have the scroll view perform
- * scrolling from a key event, just as if the event had been dispatched to
- * it by the view hierarchy.
- *
- * @param event The key event to execute.
- * @return Return true if the event was handled, else false.
- */
- public boolean executeKeyEvent(KeyEvent event) {
- mTempRect.setEmpty();
-
- if (!canScroll()) {
- if (isFocused() && event.getKeyCode() != KeyEvent.KEYCODE_BACK) {
- View currentFocused = findFocus();
- if (currentFocused == this) currentFocused = null;
- View nextFocused = FocusFinder.getInstance().findNextFocus(this,
- currentFocused, View.FOCUS_DOWN);
- return nextFocused != null
- && nextFocused != this
- && nextFocused.requestFocus(View.FOCUS_DOWN);
- }
- return false;
- }
-
- boolean handled = false;
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_DPAD_UP:
- if (!event.isAltPressed()) {
- handled = arrowScroll(View.FOCUS_UP);
- } else {
- handled = fullScroll(View.FOCUS_UP);
- }
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- if (!event.isAltPressed()) {
- handled = arrowScroll(View.FOCUS_DOWN);
- } else {
- handled = fullScroll(View.FOCUS_DOWN);
- }
- break;
- case KeyEvent.KEYCODE_SPACE:
- pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);
- break;
- }
- }
-
- return handled;
- }
-
- private boolean inChild(int x, int y) {
- if (getChildCount() > 0) {
- final int scrollY = getScrollY();
- final View child = getChildAt(0);
- return !(y < child.getTop() - scrollY
- || y >= child.getBottom() - scrollY
- || x < child.getLeft()
- || x >= child.getRight());
- }
- return false;
- }
-
- private void initOrResetVelocityTracker() {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- } else {
- mVelocityTracker.clear();
- }
- }
-
- private void initVelocityTrackerIfNotExists() {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- }
-
- private void recycleVelocityTracker() {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- }
-
- @Override
- public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- if (disallowIntercept) {
- recycleVelocityTracker();
- }
- super.requestDisallowInterceptTouchEvent(disallowIntercept);
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- /*
- * This method JUST determines whether we want to intercept the motion.
- * If we return true, onMotionEvent will be called and we do the actual
- * scrolling there.
- */
-
- /*
- * Shortcut the most recurring case: the user is in the dragging state
- * and he is moving his finger. We want to intercept this motion.
- */
- final int action = ev.getAction();
- if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
- return true;
- }
- if ((action == MotionEvent.ACTION_MOVE) && (mIsOrthoDragged)) {
- return true;
- }
- switch (action & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_MOVE: {
- /*
- * mIsBeingDragged == false, otherwise the shortcut would have
- * caught it. Check whether the user has moved far enough from his
- * original down touch.
- */
-
- /*
- * Locally do absolute value. mLastMotionY is set to the y value of
- * the down event.
- */
- final int activePointerId = mActivePointerId;
- if (activePointerId == INVALID_POINTER) {
- // If we don't have a valid id, the touch down wasn't on
- // content.
- break;
- }
-
- final int pointerIndex = ev.findPointerIndex(activePointerId);
- final float y = mHorizontal ? ev.getX(pointerIndex) : ev
- .getY(pointerIndex);
- final int yDiff = (int) Math.abs(y - mLastMotionY);
- if (yDiff > mTouchSlop) {
- mIsBeingDragged = true;
- mLastMotionY = y;
- initVelocityTrackerIfNotExists();
- mVelocityTracker.addMovement(ev);
- } else {
- final float ocoord = mHorizontal ? ev.getY(pointerIndex) : ev
- .getX(pointerIndex);
- if (Math.abs(ocoord - mLastOrthoCoord) > mTouchSlop) {
- mIsOrthoDragged = true;
- mLastOrthoCoord = ocoord;
- initVelocityTrackerIfNotExists();
- mVelocityTracker.addMovement(ev);
- }
- }
- break;
- }
-
- case MotionEvent.ACTION_DOWN: {
- final float y = mHorizontal ? ev.getX() : ev.getY();
- mDownCoords.x = ev.getX();
- mDownCoords.y = ev.getY();
- if (!inChild((int) ev.getX(), (int) ev.getY())) {
- mIsBeingDragged = false;
- recycleVelocityTracker();
- break;
- }
-
- /*
- * Remember location of down touch. ACTION_DOWN always refers to
- * pointer index 0.
- */
- mLastMotionY = y;
- mActivePointerId = ev.getPointerId(0);
-
- initOrResetVelocityTracker();
- mVelocityTracker.addMovement(ev);
- /*
- * If being flinged and user touches the screen, initiate drag;
- * otherwise don't. mScroller.isFinished should be false when being
- * flinged.
- */
- mIsBeingDragged = !mScroller.isFinished();
- mIsOrthoDragged = false;
- final float ocoord = mHorizontal ? ev.getY() : ev.getX();
- mLastOrthoCoord = ocoord;
- mDownView = findViewAt((int) ev.getX(), (int) ev.getY());
- break;
- }
-
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- if (mIsBeingDragged) {
- onScrollUp();
- }
- /* Release the drag */
- mIsBeingDragged = false;
- mIsOrthoDragged = false;
- mActivePointerId = INVALID_POINTER;
- recycleVelocityTracker();
- // TODO: this upset the single tap; need to fix later
- //if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
- // getScrollRange())) {
- // invalidate();
- //}
- break;
- case MotionEvent.ACTION_POINTER_UP:
- onSecondaryPointerUp(ev);
- break;
- }
-
- /*
- * The only time we want to intercept motion events is if we are in the
- * drag mode.
- */
- return mIsBeingDragged || mIsOrthoDragged;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- initVelocityTrackerIfNotExists();
- mVelocityTracker.addMovement(ev);
-
- final int action = ev.getAction();
- switch (action & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN: {
- mIsBeingDragged = getChildCount() != 0;
- if (!mIsBeingDragged) {
- return false;
- }
-
- /*
- * If being flinged and user touches, stop the fling. isFinished
- * will be false if being flinged.
- */
- if (!mScroller.isFinished()) {
- mScroller.abortAnimation();
- }
-
- // Remember where the motion event started
- mLastMotionY = mHorizontal ? ev.getX() : ev.getY();
- mActivePointerId = ev.getPointerId(0);
- break;
- }
- case MotionEvent.ACTION_MOVE:
- if (mIsOrthoDragged) {
- // Scroll to follow the motion event
- final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
- final float x = ev.getX(activePointerIndex);
- final float y = ev.getY(activePointerIndex);
- if (isOrthoMove(x - mDownCoords.x, y - mDownCoords.y)) {
- onOrthoDrag(mDownView, mHorizontal
- ? y - mDownCoords.y
- : x - mDownCoords.x);
- }
- } else if (mIsBeingDragged) {
- // Scroll to follow the motion event
- final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
- final float y = mHorizontal ? ev.getX(activePointerIndex)
- : ev.getY(activePointerIndex);
- final int deltaY = (int) (mLastMotionY - y);
- mLastMotionY = y;
-
- final int oldX = getScrollX();
- final int oldY = getScrollY();
- final int range = getScrollRange();
- if (mHorizontal) {
- if (overScrollBy(deltaY, 0, getScrollX(), 0, range, 0,
- mOverscrollDistance, 0, true)) {
- // Break our velocity if we hit a scroll barrier.
- mVelocityTracker.clear();
- }
- } else {
- if (overScrollBy(0, deltaY, 0, getScrollY(), 0, range,
- 0, mOverscrollDistance, true)) {
- // Break our velocity if we hit a scroll barrier.
- mVelocityTracker.clear();
- }
- }
- onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);
-
- final int overscrollMode = getOverScrollMode();
- if (overscrollMode == OVER_SCROLL_ALWAYS ||
- (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0)) {
- final int pulledToY = mHorizontal ? oldX + deltaY : oldY + deltaY;
- if (pulledToY < 0) {
- onPull(pulledToY);
- } else if (pulledToY > range) {
- onPull(pulledToY - range);
- } else {
- onPull(0);
- }
- }
- } else {
- return false;
- }
- break;
- case MotionEvent.ACTION_UP:
- final VelocityTracker vtracker = mVelocityTracker;
- vtracker.computeCurrentVelocity(1000, mMaximumVelocity);
- if (isOrthoMove(vtracker.getXVelocity(mActivePointerId),
- vtracker.getYVelocity(mActivePointerId))
- && mMinimumVelocity < Math.abs((mHorizontal ? vtracker.getYVelocity()
- : vtracker.getXVelocity()))) {
- onOrthoFling(mDownView, mHorizontal ? vtracker.getYVelocity()
- : vtracker.getXVelocity());
- break;
- }
- if (mIsOrthoDragged) {
- onOrthoDragFinished(mDownView);
- mActivePointerId = INVALID_POINTER;
- endDrag();
- } else if (mIsBeingDragged) {
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int initialVelocity = mHorizontal
- ? (int) velocityTracker.getXVelocity(mActivePointerId)
- : (int) velocityTracker.getYVelocity(mActivePointerId);
-
- if (getChildCount() > 0) {
- if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
- fling(-initialVelocity);
- } else {
- final int bottom = getScrollRange();
- if (mHorizontal) {
- if (mScroller.springBack(getScrollX(), getScrollY(), 0, bottom, 0, 0)) {
- invalidate();
- }
- } else {
- if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, bottom)) {
- invalidate();
- }
- }
- onScrollUp();
- }
- onPull(0);
- }
-
- mActivePointerId = INVALID_POINTER;
- endDrag();
- } else {
- return false;
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- if (mIsOrthoDragged) {
- onOrthoDragFinished(mDownView);
- mActivePointerId = INVALID_POINTER;
- endDrag();
- } else if (mIsBeingDragged && getChildCount() > 0) {
- if (mHorizontal) {
- if (mScroller.springBack(getScrollX(), getScrollY(), 0, getScrollRange(), 0, 0)) {
- invalidate();
- }
- } else {
- if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) {
- invalidate();
- }
- }
- // when touch is cancelled, treat it as up to allow icons to snap back
- onScrollUp();
- mActivePointerId = INVALID_POINTER;
- endDrag();
- } else {
- return false;
- }
- break;
- case MotionEvent.ACTION_POINTER_DOWN: {
- final int index = ev.getActionIndex();
- final float y = mHorizontal ? ev.getX(index) : ev.getY(index);
- mLastMotionY = y;
- mLastOrthoCoord = mHorizontal ? ev.getY(index) : ev.getX(index);
- mActivePointerId = ev.getPointerId(index);
- break;
- }
- case MotionEvent.ACTION_POINTER_UP:
- onSecondaryPointerUp(ev);
- mLastMotionY = mHorizontal
- ? ev.getX(ev.findPointerIndex(mActivePointerId))
- : ev.getY(ev.findPointerIndex(mActivePointerId));
- break;
- }
- return true;
- }
-
- protected void onScrollUp() {
- }
-
- protected View findViewAt(int x, int y) {
- // subclass responsibility
- return null;
- }
-
- protected void onPull(int delta) {
- }
-
- private void onSecondaryPointerUp(MotionEvent ev) {
- final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
- MotionEvent.ACTION_POINTER_INDEX_SHIFT;
- final int pointerId = ev.getPointerId(pointerIndex);
- if (pointerId == mActivePointerId) {
- // This was our active pointer going up. Choose a new
- // active pointer and adjust accordingly.
- // TODO: Make this decision more intelligent.
- final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
- mLastMotionY = mHorizontal ? ev.getX(newPointerIndex) : ev.getY(newPointerIndex);
- mActivePointerId = ev.getPointerId(newPointerIndex);
- if (mVelocityTracker != null) {
- mVelocityTracker.clear();
- }
- mLastOrthoCoord = mHorizontal ? ev.getY(newPointerIndex)
- : ev.getX(newPointerIndex);
- }
- }
-
- @Override
- public boolean onGenericMotionEvent(MotionEvent event) {
- if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_SCROLL: {
- if (!mIsBeingDragged) {
- if (mHorizontal) {
- final float hscroll = event
- .getAxisValue(MotionEvent.AXIS_HSCROLL);
- if (hscroll != 0) {
- final int delta = (int) (hscroll * mScrollFactor);
- final int range = getScrollRange();
- int oldScrollX = getScrollX();
- int newScrollX = oldScrollX - delta;
- if (newScrollX < 0) {
- newScrollX = 0;
- } else if (newScrollX > range) {
- newScrollX = range;
- }
- if (newScrollX != oldScrollX) {
- super.scrollTo(newScrollX, getScrollY());
- return true;
- }
- }
- } else {
- final float vscroll = event
- .getAxisValue(MotionEvent.AXIS_VSCROLL);
- if (vscroll != 0) {
- final int delta = (int) (vscroll * mScrollFactor);
- final int range = getScrollRange();
- int oldScrollY = getScrollY();
- int newScrollY = oldScrollY - delta;
- if (newScrollY < 0) {
- newScrollY = 0;
- } else if (newScrollY > range) {
- newScrollY = range;
- }
- if (newScrollY != oldScrollY) {
- super.scrollTo(getScrollX(), newScrollY);
- return true;
- }
- }
- }
- }
- }
- }
- }
- return super.onGenericMotionEvent(event);
- }
-
- protected void onOrthoDrag(View draggedView, float distance) {
- }
-
- protected void onOrthoDragFinished(View draggedView) {
- }
-
- protected void onOrthoFling(View draggedView, float velocity) {
- }
-
- @Override
- protected void onOverScrolled(int scrollX, int scrollY,
- boolean clampedX, boolean clampedY) {
- // Treat animating scrolls differently; see #computeScroll() for why.
- if (!mScroller.isFinished()) {
- // workaround
- // need to do this because can't set scroll offset before API 14
- mIgnoreScroll = true;
- super.scrollTo(scrollX, scrollY);
- mIgnoreScroll = false;
- // original code:
- // mScrollX = scrollX;
- // mScrollY = scrollY;
- // invalidateParentIfNeeded();
- if (mHorizontal && clampedX) {
- mScroller.springBack(getScrollX(), getScrollY(), 0, getScrollRange(), 0, 0);
- } else if (!mHorizontal && clampedY) {
- mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange());
- }
- } else {
- super.scrollTo(scrollX, scrollY);
- }
- awakenScrollBars();
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setScrollable(true);
- }
-
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setScrollable(true);
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- // Do not append text content to scroll events they are fired frequently
- // and the client has already received another event type with the text.
- if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) {
- super.dispatchPopulateAccessibilityEvent(event);
- }
- return false;
- }
-
- private int getScrollRange() {
- int scrollRange = 0;
- if (getChildCount() > 0) {
- View child = getChildAt(0);
- if (mHorizontal) {
- scrollRange = Math.max(0,
- child.getWidth() - (getWidth() - getPaddingRight() - getPaddingLeft()));
- } else {
- scrollRange = Math.max(0,
- child.getHeight() - (getHeight() - getPaddingBottom() - getPaddingTop()));
- }
- }
- return scrollRange;
- }
-
- /**
- * <p>
- * Finds the next focusable component that fits in this View's bounds
- * (excluding fading edges) pretending that this View's top is located at
- * the parameter top.
- * </p>
- *
- * @param topFocus look for a candidate at the top of the bounds if topFocus is true,
- * or at the bottom of the bounds if topFocus is false
- * @param top the top offset of the bounds in which a focusable must be
- * found (the fading edge is assumed to start at this position)
- * @param preferredFocusable the View that has highest priority and will be
- * returned if it is within my bounds (null is valid)
- * @return the next focusable component in the bounds or null if none can be found
- */
- private View findFocusableViewInMyBounds(final boolean topFocus,
- final int top, View preferredFocusable) {
- /*
- * The fading edge's transparent side should be considered for focus
- * since it's mostly visible, so we divide the actual fading edge length
- * by 2.
- */
- final int fadingEdgeLength = (mHorizontal
- ? getHorizontalFadingEdgeLength()
- : getVerticalFadingEdgeLength()) / 2;
- final int topWithoutFadingEdge = top + fadingEdgeLength;
- final int bottomWithoutFadingEdge = top + (mHorizontal ? getWidth() : getHeight()) - fadingEdgeLength;
-
- if ((preferredFocusable != null)
- && ((mHorizontal ? preferredFocusable.getLeft() : preferredFocusable.getTop())
- < bottomWithoutFadingEdge)
- && ((mHorizontal ? preferredFocusable.getRight() : preferredFocusable.getBottom()) > topWithoutFadingEdge)) {
- return preferredFocusable;
- }
-
- return findFocusableViewInBounds(topFocus, topWithoutFadingEdge,
- bottomWithoutFadingEdge);
- }
-
- /**
- * <p>
- * Finds the next focusable component that fits in the specified bounds.
- * </p>
- *
- * @param topFocus look for a candidate is the one at the top of the bounds
- * if topFocus is true, or at the bottom of the bounds if topFocus is
- * false
- * @param top the top offset of the bounds in which a focusable must be
- * found
- * @param bottom the bottom offset of the bounds in which a focusable must
- * be found
- * @return the next focusable component in the bounds or null if none can
- * be found
- */
- private View findFocusableViewInBounds(boolean topFocus, int top, int bottom) {
-
- List<View> focusables = getFocusables(View.FOCUS_FORWARD);
- View focusCandidate = null;
-
- /*
- * A fully contained focusable is one where its top is below the bound's
- * top, and its bottom is above the bound's bottom. A partially
- * contained focusable is one where some part of it is within the
- * bounds, but it also has some part that is not within bounds. A fully contained
- * focusable is preferred to a partially contained focusable.
- */
- boolean foundFullyContainedFocusable = false;
-
- int count = focusables.size();
- for (int i = 0; i < count; i++) {
- View view = focusables.get(i);
- int viewTop = mHorizontal ? view.getLeft() : view.getTop();
- int viewBottom = mHorizontal ? view.getRight() : view.getBottom();
-
- if (top < viewBottom && viewTop < bottom) {
- /*
- * the focusable is in the target area, it is a candidate for
- * focusing
- */
-
- final boolean viewIsFullyContained = (top < viewTop) &&
- (viewBottom < bottom);
-
- if (focusCandidate == null) {
- /* No candidate, take this one */
- focusCandidate = view;
- foundFullyContainedFocusable = viewIsFullyContained;
- } else {
- final int ctop = mHorizontal ? focusCandidate.getLeft() : focusCandidate.getTop();
- final int cbot = mHorizontal ? focusCandidate.getRight() : focusCandidate.getBottom();
- final boolean viewIsCloserToBoundary =
- (topFocus && viewTop < ctop) ||
- (!topFocus && viewBottom > cbot);
-
- if (foundFullyContainedFocusable) {
- if (viewIsFullyContained && viewIsCloserToBoundary) {
- /*
- * We're dealing with only fully contained views, so
- * it has to be closer to the boundary to beat our
- * candidate
- */
- focusCandidate = view;
- }
- } else {
- if (viewIsFullyContained) {
- /* Any fully contained view beats a partially contained view */
- focusCandidate = view;
- foundFullyContainedFocusable = true;
- } else if (viewIsCloserToBoundary) {
- /*
- * Partially contained view beats another partially
- * contained view if it's closer
- */
- focusCandidate = view;
- }
- }
- }
- }
- }
-
- return focusCandidate;
- }
-
- // i was here
-
- /**
- * <p>Handles scrolling in response to a "page up/down" shortcut press. This
- * method will scroll the view by one page up or down and give the focus
- * to the topmost/bottommost component in the new visible area. If no
- * component is a good candidate for focus, this scrollview reclaims the
- * focus.</p>
- *
- * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
- * to go one page up or
- * {@link android.view.View#FOCUS_DOWN} to go one page down
- * @return true if the key event is consumed by this method, false otherwise
- */
- public boolean pageScroll(int direction) {
- boolean down = direction == View.FOCUS_DOWN;
- int height = getHeight();
-
- if (down) {
- mTempRect.top = getScrollY() + height;
- int count = getChildCount();
- if (count > 0) {
- View view = getChildAt(count - 1);
- if (mTempRect.top + height > view.getBottom()) {
- mTempRect.top = view.getBottom() - height;
- }
- }
- } else {
- mTempRect.top = getScrollY() - height;
- if (mTempRect.top < 0) {
- mTempRect.top = 0;
- }
- }
- mTempRect.bottom = mTempRect.top + height;
-
- return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
- }
-
- /**
- * <p>Handles scrolling in response to a "home/end" shortcut press. This
- * method will scroll the view to the top or bottom and give the focus
- * to the topmost/bottommost component in the new visible area. If no
- * component is a good candidate for focus, this scrollview reclaims the
- * focus.</p>
- *
- * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
- * to go the top of the view or
- * {@link android.view.View#FOCUS_DOWN} to go the bottom
- * @return true if the key event is consumed by this method, false otherwise
- */
- public boolean fullScroll(int direction) {
- boolean down = direction == View.FOCUS_DOWN;
- int height = getHeight();
-
- mTempRect.top = 0;
- mTempRect.bottom = height;
-
- if (down) {
- int count = getChildCount();
- if (count > 0) {
- View view = getChildAt(count - 1);
- mTempRect.bottom = view.getBottom() + getPaddingBottom();
- mTempRect.top = mTempRect.bottom - height;
- }
- }
-
- return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
- }
-
- /**
- * <p>Scrolls the view to make the area defined by <code>top</code> and
- * <code>bottom</code> visible. This method attempts to give the focus
- * to a component visible in this area. If no component can be focused in
- * the new visible area, the focus is reclaimed by this ScrollView.</p>
- *
- * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
- * to go upward, {@link android.view.View#FOCUS_DOWN} to downward
- * @param top the top offset of the new area to be made visible
- * @param bottom the bottom offset of the new area to be made visible
- * @return true if the key event is consumed by this method, false otherwise
- */
- private boolean scrollAndFocus(int direction, int top, int bottom) {
- boolean handled = true;
-
- int height = getHeight();
- int containerTop = getScrollY();
- int containerBottom = containerTop + height;
- boolean up = direction == View.FOCUS_UP;
-
- View newFocused = findFocusableViewInBounds(up, top, bottom);
- if (newFocused == null) {
- newFocused = this;
- }
-
- if (top >= containerTop && bottom <= containerBottom) {
- handled = false;
- } else {
- int delta = up ? (top - containerTop) : (bottom - containerBottom);
- doScrollY(delta);
- }
-
- if (newFocused != findFocus()) newFocused.requestFocus(direction);
-
- return handled;
- }
-
- /**
- * Handle scrolling in response to an up or down arrow click.
- *
- * @param direction The direction corresponding to the arrow key that was
- * pressed
- * @return True if we consumed the event, false otherwise
- */
- public boolean arrowScroll(int direction) {
-
- View currentFocused = findFocus();
- if (currentFocused == this) currentFocused = null;
-
- View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
-
- final int maxJump = getMaxScrollAmount();
-
- if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump, getHeight())) {
- nextFocused.getDrawingRect(mTempRect);
- offsetDescendantRectToMyCoords(nextFocused, mTempRect);
- int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
- doScrollY(scrollDelta);
- nextFocused.requestFocus(direction);
- } else {
- // no new focus
- int scrollDelta = maxJump;
-
- if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {
- scrollDelta = getScrollY();
- } else if (direction == View.FOCUS_DOWN) {
- if (getChildCount() > 0) {
- int daBottom = getChildAt(0).getBottom();
- int screenBottom = getScrollY() + getHeight() - getPaddingBottom();
- if (daBottom - screenBottom < maxJump) {
- scrollDelta = daBottom - screenBottom;
- }
- }
- }
- if (scrollDelta == 0) {
- return false;
- }
- doScrollY(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
- }
-
- if (currentFocused != null && currentFocused.isFocused()
- && isOffScreen(currentFocused)) {
- // previously focused item still has focus and is off screen, give
- // it up (take it back to ourselves)
- // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
- // sure to
- // get it)
- final int descendantFocusability = getDescendantFocusability(); // save
- setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
- requestFocus();
- setDescendantFocusability(descendantFocusability); // restore
- }
- return true;
- }
-
- private boolean isOrthoMove(float moveX, float moveY) {
- return mHorizontal && Math.abs(moveY) > Math.abs(moveX)
- || !mHorizontal && Math.abs(moveX) > Math.abs(moveY);
- }
-
- /**
- * @return whether the descendant of this scroll view is scrolled off
- * screen.
- */
- private boolean isOffScreen(View descendant) {
- if (mHorizontal) {
- return !isWithinDeltaOfScreen(descendant, getWidth(), 0);
- } else {
- return !isWithinDeltaOfScreen(descendant, 0, getHeight());
- }
- }
-
- /**
- * @return whether the descendant of this scroll view is within delta
- * pixels of being on the screen.
- */
- private boolean isWithinDeltaOfScreen(View descendant, int delta, int height) {
- descendant.getDrawingRect(mTempRect);
- offsetDescendantRectToMyCoords(descendant, mTempRect);
- if (mHorizontal) {
- return (mTempRect.right + delta) >= getScrollX()
- && (mTempRect.left - delta) <= (getScrollX() + height);
- } else {
- return (mTempRect.bottom + delta) >= getScrollY()
- && (mTempRect.top - delta) <= (getScrollY() + height);
- }
- }
-
- /**
- * Smooth scroll by a Y delta
- *
- * @param delta the number of pixels to scroll by on the Y axis
- */
- private void doScrollY(int delta) {
- if (delta != 0) {
- if (mSmoothScrollingEnabled) {
- if (mHorizontal) {
- smoothScrollBy(0, delta);
- } else {
- smoothScrollBy(delta, 0);
- }
- } else {
- if (mHorizontal) {
- scrollBy(0, delta);
- } else {
- scrollBy(delta, 0);
- }
- }
- }
- }
-
- /**
- * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
- *
- * @param dx the number of pixels to scroll by on the X axis
- * @param dy the number of pixels to scroll by on the Y axis
- */
- public final void smoothScrollBy(int dx, int dy) {
- if (getChildCount() == 0) {
- // Nothing to do.
- return;
- }
- long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
- if (duration > ANIMATED_SCROLL_GAP) {
- if (mHorizontal) {
- final int width = getWidth() - getPaddingRight() - getPaddingLeft();
- final int right = getChildAt(0).getWidth();
- final int maxX = Math.max(0, right - width);
- final int scrollX = getScrollX();
- dx = Math.max(0, Math.min(scrollX + dx, maxX)) - scrollX;
- mScroller.startScroll(scrollX, getScrollY(), dx, 0);
- } else {
- final int height = getHeight() - getPaddingBottom() - getPaddingTop();
- final int bottom = getChildAt(0).getHeight();
- final int maxY = Math.max(0, bottom - height);
- final int scrollY = getScrollY();
- dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;
- mScroller.startScroll(getScrollX(), scrollY, 0, dy);
- }
- invalidate();
- } else {
- if (!mScroller.isFinished()) {
- mScroller.abortAnimation();
- }
- scrollBy(dx, dy);
- }
- mLastScroll = AnimationUtils.currentAnimationTimeMillis();
- }
-
- /**
- * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
- *
- * @param x the position where to scroll on the X axis
- * @param y the position where to scroll on the Y axis
- */
- public final void smoothScrollTo(int x, int y) {
- smoothScrollBy(x - getScrollX(), y - getScrollY());
- }
-
- /**
- * <p>
- * The scroll range of a scroll view is the overall height of all of its
- * children.
- * </p>
- */
- @Override
- protected int computeVerticalScrollRange() {
- if (mHorizontal) {
- return super.computeVerticalScrollRange();
- }
- final int count = getChildCount();
- final int contentHeight = getHeight() - getPaddingBottom() - getPaddingTop();
- if (count == 0) {
- return contentHeight;
- }
-
- int scrollRange = getChildAt(0).getBottom();
- final int scrollY = getScrollY();
- final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
- if (scrollY < 0) {
- scrollRange -= scrollY;
- } else if (scrollY > overscrollBottom) {
- scrollRange += scrollY - overscrollBottom;
- }
-
- return scrollRange;
- }
-
- /**
- * <p>
- * The scroll range of a scroll view is the overall height of all of its
- * children.
- * </p>
- */
- @Override
- protected int computeHorizontalScrollRange() {
- if (!mHorizontal) {
- return super.computeHorizontalScrollRange();
- }
- final int count = getChildCount();
- final int contentWidth = getWidth() - getPaddingRight() - getPaddingLeft();
- if (count == 0) {
- return contentWidth;
- }
-
- int scrollRange = getChildAt(0).getRight();
- final int scrollX = getScrollX();
- final int overscrollBottom = Math.max(0, scrollRange - contentWidth);
- if (scrollX < 0) {
- scrollRange -= scrollX;
- } else if (scrollX > overscrollBottom) {
- scrollRange += scrollX - overscrollBottom;
- }
-
- return scrollRange;
- }
-
- @Override
- protected int computeVerticalScrollOffset() {
- return Math.max(0, super.computeVerticalScrollOffset());
- }
-
- @Override
- protected int computeHorizontalScrollOffset() {
- return Math.max(0, super.computeHorizontalScrollOffset());
- }
-
- @Override
- protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
- ViewGroup.LayoutParams lp = child.getLayoutParams();
-
- int childWidthMeasureSpec;
- int childHeightMeasureSpec;
-
- if (mHorizontal) {
- childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, getPaddingTop()
- + getPaddingBottom(), lp.height);
-
- childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- } else {
- childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, getPaddingLeft()
- + getPaddingRight(), lp.width);
-
- childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- }
-
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
-
- @Override
- protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
- int parentHeightMeasureSpec, int heightUsed) {
- final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
-
- int childWidthMeasureSpec;
- int childHeightMeasureSpec;
- if (mHorizontal) {
- childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
- getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin
- + heightUsed, lp.height);
- childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
- lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);
- } else {
- childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
- getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin
- + widthUsed, lp.width);
- childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
- lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
- }
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
-
- @Override
- public void computeScroll() {
- if (mScroller.computeScrollOffset()) {
- // This is called at drawing time by ViewGroup. We don't want to
- // re-show the scrollbars at this point, which scrollTo will do,
- // so we replicate most of scrollTo here.
- //
- // It's a little odd to call onScrollChanged from inside the drawing.
- //
- // It is, except when you remember that computeScroll() is used to
- // animate scrolling. So unless we want to defer the onScrollChanged()
- // until the end of the animated scrolling, we don't really have a
- // choice here.
- //
- // I agree. The alternative, which I think would be worse, is to post
- // something and tell the subclasses later. This is bad because there
- // will be a window where getScrollX()/Y is different from what the app
- // thinks it is.
- //
- int oldX = getScrollX();
- int oldY = getScrollY();
- int x = mScroller.getCurrX();
- int y = mScroller.getCurrY();
-
- if (oldX != x || oldY != y) {
- if (mHorizontal) {
- overScrollBy(x - oldX, y - oldY, oldX, oldY, getScrollRange(), 0,
- mOverflingDistance, 0, false);
- } else {
- overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, getScrollRange(),
- 0, mOverflingDistance, false);
- }
- onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);
- }
- awakenScrollBars();
-
- // Keep on drawing until the animation has finished.
- postInvalidate();
- }
- }
-
- /**
- * Scrolls the view to the given child.
- *
- * @param child the View to scroll to
- */
- private void scrollToChild(View child) {
- child.getDrawingRect(mTempRect);
-
- /* Offset from child's local coordinates to ScrollView coordinates */
- offsetDescendantRectToMyCoords(child, mTempRect);
- scrollToChildRect(mTempRect, true);
- }
-
- /**
- * If rect is off screen, scroll just enough to get it (or at least the
- * first screen size chunk of it) on screen.
- *
- * @param rect The rectangle.
- * @param immediate True to scroll immediately without animation
- * @return true if scrolling was performed
- */
- private boolean scrollToChildRect(Rect rect, boolean immediate) {
- final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
- final boolean scroll = delta != 0;
- if (scroll) {
- if (immediate) {
- if (mHorizontal) {
- scrollBy(delta, 0);
- } else {
- scrollBy(0, delta);
- }
- } else {
- if (mHorizontal) {
- smoothScrollBy(delta, 0);
- } else {
- smoothScrollBy(0, delta);
- }
- }
- }
- return scroll;
- }
-
- /**
- * Compute the amount to scroll in the Y direction in order to get
- * a rectangle completely on the screen (or, if taller than the screen,
- * at least the first screen size chunk of it).
- *
- * @param rect The rect.
- * @return The scroll delta.
- */
- protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
- if (mHorizontal) {
- return computeScrollDeltaToGetChildRectOnScreenHorizontal(rect);
- } else {
- return computeScrollDeltaToGetChildRectOnScreenVertical(rect);
- }
- }
-
- private int computeScrollDeltaToGetChildRectOnScreenVertical(Rect rect) {
- if (getChildCount() == 0) return 0;
-
- int height = getHeight();
- int screenTop = getScrollY();
- int screenBottom = screenTop + height;
-
- int fadingEdge = getVerticalFadingEdgeLength();
-
- // leave room for top fading edge as long as rect isn't at very top
- if (rect.top > 0) {
- screenTop += fadingEdge;
- }
-
- // leave room for bottom fading edge as long as rect isn't at very bottom
- if (rect.bottom < getChildAt(0).getHeight()) {
- screenBottom -= fadingEdge;
- }
-
- int scrollYDelta = 0;
-
- if (rect.bottom > screenBottom && rect.top > screenTop) {
- // need to move down to get it in view: move down just enough so
- // that the entire rectangle is in view (or at least the first
- // screen size chunk).
-
- if (rect.height() > height) {
- // just enough to get screen size chunk on
- scrollYDelta += (rect.top - screenTop);
- } else {
- // get entire rect at bottom of screen
- scrollYDelta += (rect.bottom - screenBottom);
- }
-
- // make sure we aren't scrolling beyond the end of our content
- int bottom = getChildAt(0).getBottom();
- int distanceToBottom = bottom - screenBottom;
- scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
-
- } else if (rect.top < screenTop && rect.bottom < screenBottom) {
- // need to move up to get it in view: move up just enough so that
- // entire rectangle is in view (or at least the first screen
- // size chunk of it).
-
- if (rect.height() > height) {
- // screen size chunk
- scrollYDelta -= (screenBottom - rect.bottom);
- } else {
- // entire rect at top
- scrollYDelta -= (screenTop - rect.top);
- }
-
- // make sure we aren't scrolling any further than the top our content
- scrollYDelta = Math.max(scrollYDelta, -getScrollY());
- }
- return scrollYDelta;
- }
-
- private int computeScrollDeltaToGetChildRectOnScreenHorizontal(Rect rect) {
- if (getChildCount() == 0) return 0;
-
- int width = getWidth();
- int screenLeft = getScrollX();
- int screenRight = screenLeft + width;
-
- int fadingEdge = getHorizontalFadingEdgeLength();
-
- // leave room for left fading edge as long as rect isn't at very left
- if (rect.left > 0) {
- screenLeft += fadingEdge;
- }
-
- // leave room for right fading edge as long as rect isn't at very right
- if (rect.right < getChildAt(0).getWidth()) {
- screenRight -= fadingEdge;
- }
-
- int scrollXDelta = 0;
-
- if (rect.right > screenRight && rect.left > screenLeft) {
- // need to move right to get it in view: move right just enough so
- // that the entire rectangle is in view (or at least the first
- // screen size chunk).
-
- if (rect.width() > width) {
- // just enough to get screen size chunk on
- scrollXDelta += (rect.left - screenLeft);
- } else {
- // get entire rect at right of screen
- scrollXDelta += (rect.right - screenRight);
- }
-
- // make sure we aren't scrolling beyond the end of our content
- int right = getChildAt(0).getRight();
- int distanceToRight = right - screenRight;
- scrollXDelta = Math.min(scrollXDelta, distanceToRight);
-
- } else if (rect.left < screenLeft && rect.right < screenRight) {
- // need to move right to get it in view: move right just enough so that
- // entire rectangle is in view (or at least the first screen
- // size chunk of it).
-
- if (rect.width() > width) {
- // screen size chunk
- scrollXDelta -= (screenRight - rect.right);
- } else {
- // entire rect at left
- scrollXDelta -= (screenLeft - rect.left);
- }
-
- // make sure we aren't scrolling any further than the left our content
- scrollXDelta = Math.max(scrollXDelta, -getScrollX());
- }
- return scrollXDelta;
- }
-
-
- @Override
- public void requestChildFocus(View child, View focused) {
- if (!mIsLayoutDirty) {
- scrollToChild(focused);
- } else {
- // The child may not be laid out yet, we can't compute the scroll yet
- mChildToScrollTo = focused;
- }
- super.requestChildFocus(child, focused);
- }
-
-
- /**
- * When looking for focus in children of a scroll view, need to be a little
- * more careful not to give focus to something that is scrolled off screen.
- *
- * This is more expensive than the default {@link android.view.ViewGroup}
- * implementation, otherwise this behavior might have been made the default.
- */
- @Override
- protected boolean onRequestFocusInDescendants(int direction,
- Rect previouslyFocusedRect) {
-
- // convert from forward / backward notation to up / down / left / right
- // (ugh).
- if (mHorizontal) {
- if (direction == View.FOCUS_FORWARD) {
- direction = View.FOCUS_RIGHT;
- } else if (direction == View.FOCUS_BACKWARD) {
- direction = View.FOCUS_LEFT;
- }
- } else {
- if (direction == View.FOCUS_FORWARD) {
- direction = View.FOCUS_DOWN;
- } else if (direction == View.FOCUS_BACKWARD) {
- direction = View.FOCUS_UP;
- }
- }
-
- final View nextFocus = previouslyFocusedRect == null ?
- FocusFinder.getInstance().findNextFocus(this, null, direction) :
- FocusFinder.getInstance().findNextFocusFromRect(this,
- previouslyFocusedRect, direction);
-
- if (nextFocus == null) {
- return false;
- }
-
- if (isOffScreen(nextFocus)) {
- return false;
- }
-
- return nextFocus.requestFocus(direction, previouslyFocusedRect);
- }
-
- @Override
- public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
- boolean immediate) {
- // offset into coordinate space of this scroll view
- rectangle.offset(child.getLeft() - child.getScrollX(),
- child.getTop() - child.getScrollY());
-
- return scrollToChildRect(rectangle, immediate);
- }
-
- @Override
- public void requestLayout() {
- mIsLayoutDirty = true;
- super.requestLayout();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- mIsLayoutDirty = false;
- // Give a child focus if it needs it
- if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
- scrollToChild(mChildToScrollTo);
- }
- mChildToScrollTo = null;
-
- // Calling this with the present values causes it to re-clam them
- scrollTo(getScrollX(), getScrollY());
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
-
- View currentFocused = findFocus();
- if (null == currentFocused || this == currentFocused)
- return;
-
- // If the currently-focused view was visible on the screen when the
- // screen was at the old height, then scroll the screen to make that
- // view visible with the new screen height.
- if (isWithinDeltaOfScreen(currentFocused, 0, oldh)) {
- currentFocused.getDrawingRect(mTempRect);
- offsetDescendantRectToMyCoords(currentFocused, mTempRect);
- int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
- doScrollY(scrollDelta);
- }
- }
-
- /**
- * Return true if child is an descendant of parent, (or equal to the parent).
- */
- private boolean isViewDescendantOf(View child, View parent) {
- if (child == parent) {
- return true;
- }
-
- final ViewParent theParent = child.getParent();
- return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
- }
-
- /**
- * Fling the scroll view
- *
- * @param velocityY The initial velocity in the Y direction. Positive
- * numbers mean that the finger/cursor is moving down the screen,
- * which means we want to scroll towards the top.
- */
- public void fling(int velocityY) {
- if (getChildCount() > 0) {
- if (mHorizontal) {
- int width = getWidth() - getPaddingRight() - getPaddingLeft();
- int right = getChildAt(0).getWidth();
-
- mScroller.fling(getScrollX(), getScrollY(), velocityY, 0,
- 0, Math.max(0, right - width), 0, 0, width/2, 0);
- } else {
- int height = getHeight() - getPaddingBottom() - getPaddingTop();
- int bottom = getChildAt(0).getHeight();
-
- mScroller.fling(getScrollX(), getScrollY(), 0, velocityY, 0, 0, 0,
- Math.max(0, bottom - height), 0, height/2);
- }
- invalidate();
- }
- }
-
- private void endDrag() {
- mIsBeingDragged = false;
- mIsOrthoDragged = false;
- mDownView = null;
- recycleVelocityTracker();
- }
-
- /**
- * {@inheritDoc}
- *
- * <p>This version also clamps the scrolling to the bounds of our child.
- */
- @Override
- public void scrollTo(int x, int y) {
- // we rely on the fact the View.scrollBy calls scrollTo.
- if (getChildCount() > 0) {
- View child = getChildAt(0);
- x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());
- y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());
- if (x != getScrollX() || y != getScrollY()) {
- super.scrollTo(x, y);
- }
- }
- }
-
- private int clamp(int n, int my, int child) {
- if (my >= child || n < 0) {
- /* my >= child is this case:
- * |--------------- me ---------------|
- * |------ child ------|
- * or
- * |--------------- me ---------------|
- * |------ child ------|
- * or
- * |--------------- me ---------------|
- * |------ child ------|
- *
- * n < 0 is this case:
- * |------ me ------|
- * |-------- child --------|
- * |-- mScrollX --|
- */
- return 0;
- }
- if ((my+n) > child) {
- /* this case:
- * |------ me ------|
- * |------ child ------|
- * |-- mScrollX --|
- */
- return child-my;
- }
- return n;
- }
-
-}