summaryrefslogtreecommitdiffstats
path: root/core/java/com
diff options
context:
space:
mode:
authorAdam Powell <adamp@google.com>2015-10-12 23:33:22 +0000
committerAndroid Git Automerger <android-git-automerger@android.com>2015-10-12 23:33:22 +0000
commit40ce70c9c00144ede8d415a92dc411a53f2316f4 (patch)
tree1056bfac7f6280d7da93e5164369763ebaeeb99d /core/java/com
parent829920ede03dbfd51f803940faa27fa7bf15063c (diff)
parent11af18783d32f8646428500a0ff3a2a57a77bf95 (diff)
downloadframeworks_base-40ce70c9c00144ede8d415a92dc411a53f2316f4.zip
frameworks_base-40ce70c9c00144ede8d415a92dc411a53f2316f4.tar.gz
frameworks_base-40ce70c9c00144ede8d415a92dc411a53f2316f4.tar.bz2
am 11af1878: Merge "Add animation and positional stability to intent chooser UI" into mnc-dr-dev
* commit '11af18783d32f8646428500a0ff3a2a57a77bf95': Add animation and positional stability to intent chooser UI
Diffstat (limited to 'core/java/com')
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java273
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java8
-rw-r--r--core/java/com/android/internal/widget/ResolverDrawerLayout.java111
3 files changed, 335 insertions, 57 deletions
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 2e0bf06..6bbebb7 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -16,6 +16,8 @@
package com.android.internal.app;
+import android.animation.ObjectAnimator;
+import android.annotation.NonNull;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
@@ -29,6 +31,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.database.DataSetObserver;
+import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
@@ -46,13 +49,18 @@ import android.service.chooser.ChooserTargetService;
import android.service.chooser.IChooserTargetResult;
import android.service.chooser.IChooserTargetService;
import android.text.TextUtils;
+import android.util.FloatProperty;
import android.util.Log;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.MeasureSpec;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ListView;
@@ -80,6 +88,7 @@ public class ChooserActivity extends ResolverActivity {
private Intent mReferrerFillInIntent;
private ChooserListAdapter mChooserListAdapter;
+ private ChooserRowAdapter mChooserRowAdapter;
private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
@@ -253,7 +262,9 @@ public class ChooserActivity extends ResolverActivity {
boolean alwaysUseOption) {
final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
mChooserListAdapter = (ChooserListAdapter) adapter;
- adapterView.setAdapter(new ChooserRowAdapter(mChooserListAdapter));
+ mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
+ mChooserRowAdapter.registerDataSetObserver(new OffsetDataSetObserver(adapterView));
+ adapterView.setAdapter(mChooserRowAdapter);
if (listView != null) {
listView.setItemsCanFocus(true);
}
@@ -913,19 +924,103 @@ public class ChooserActivity extends ResolverActivity {
}
}
+ static class RowScale {
+ private static final int DURATION = 400;
+
+ float mScale;
+ ChooserRowAdapter mAdapter;
+ private final ObjectAnimator mAnimator;
+
+ public static final FloatProperty<RowScale> PROPERTY =
+ new FloatProperty<RowScale>("scale") {
+ @Override
+ public void setValue(RowScale object, float value) {
+ object.mScale = value;
+ object.mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public Float get(RowScale object) {
+ return object.mScale;
+ }
+ };
+
+ public RowScale(@NonNull ChooserRowAdapter adapter, float from, float to) {
+ mAdapter = adapter;
+ mScale = from;
+ if (from == to) {
+ mAnimator = null;
+ return;
+ }
+
+ mAnimator = ObjectAnimator.ofFloat(this, PROPERTY, from, to).setDuration(DURATION);
+ }
+
+ public RowScale setInterpolator(Interpolator interpolator) {
+ if (mAnimator != null) {
+ mAnimator.setInterpolator(interpolator);
+ }
+ return this;
+ }
+
+ public float get() {
+ return mScale;
+ }
+
+ public void startAnimation() {
+ if (mAnimator != null) {
+ mAnimator.start();
+ }
+ }
+
+ public void cancelAnimation() {
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ }
+ }
+
class ChooserRowAdapter extends BaseAdapter {
private ChooserListAdapter mChooserListAdapter;
private final LayoutInflater mLayoutInflater;
private final int mColumnCount = 4;
+ private RowScale[] mServiceTargetScale;
+ private final Interpolator mInterpolator;
public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
mChooserListAdapter = wrappedAdapter;
mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
+ mInterpolator = AnimationUtils.loadInterpolator(ChooserActivity.this,
+ android.R.interpolator.decelerate_quint);
+
wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
@Override
public void onChanged() {
super.onChanged();
+ final int rcount = getServiceTargetRowCount();
+ if (mServiceTargetScale == null
+ || mServiceTargetScale.length != rcount) {
+ RowScale[] old = mServiceTargetScale;
+ int oldRCount = old != null ? old.length : 0;
+ mServiceTargetScale = new RowScale[rcount];
+ if (old != null && rcount > 0) {
+ System.arraycopy(old, 0, mServiceTargetScale, 0,
+ Math.min(old.length, rcount));
+ }
+
+ for (int i = rcount; i < oldRCount; i++) {
+ old[i].cancelAnimation();
+ }
+
+ for (int i = oldRCount; i < rcount; i++) {
+ final RowScale rs = new RowScale(ChooserRowAdapter.this, 0.f, 1.f)
+ .setInterpolator(mInterpolator);
+ mServiceTargetScale[i] = rs;
+ rs.startAnimation();
+ }
+ }
+
notifyDataSetChanged();
}
@@ -933,19 +1028,43 @@ public class ChooserActivity extends ResolverActivity {
public void onInvalidated() {
super.onInvalidated();
notifyDataSetInvalidated();
+ if (mServiceTargetScale != null) {
+ for (RowScale rs : mServiceTargetScale) {
+ rs.cancelAnimation();
+ }
+ }
}
});
}
+ private float getRowScale(int rowPosition) {
+ final int start = getCallerTargetRowCount();
+ final int end = start + getServiceTargetRowCount();
+ if (rowPosition >= start && rowPosition < end) {
+ return mServiceTargetScale[rowPosition - start].get();
+ }
+ return 1.f;
+ }
+
@Override
public int getCount() {
return (int) (
- Math.ceil((float) mChooserListAdapter.getCallerTargetCount() / mColumnCount)
- + Math.ceil((float) mChooserListAdapter.getServiceTargetCount() / mColumnCount)
+ getCallerTargetRowCount()
+ + getServiceTargetRowCount()
+ Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
);
}
+ public int getCallerTargetRowCount() {
+ return (int) Math.ceil(
+ (float) mChooserListAdapter.getCallerTargetCount() / mColumnCount);
+ }
+
+ public int getServiceTargetRowCount() {
+ return (int) Math.ceil(
+ (float) mChooserListAdapter.getServiceTargetCount() / mColumnCount);
+ }
+
@Override
public Object getItem(int position) {
// We have nothing useful to return here.
@@ -959,33 +1078,67 @@ public class ChooserActivity extends ResolverActivity {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- final View[] holder;
+ final RowViewHolder holder;
if (convertView == null) {
holder = createViewHolder(parent);
} else {
- holder = (View[]) convertView.getTag();
+ holder = (RowViewHolder) convertView.getTag();
}
bindViewHolder(position, holder);
- // We keep the actual list item view as the last item in the holder array
- return holder[mColumnCount];
+ return holder.row;
}
- View[] createViewHolder(ViewGroup parent) {
- final View[] holder = new View[mColumnCount + 1];
-
+ RowViewHolder createViewHolder(ViewGroup parent) {
final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
parent, false);
+ final RowViewHolder holder = new RowViewHolder(row, mColumnCount);
+ final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
for (int i = 0; i < mColumnCount; i++) {
- holder[i] = mChooserListAdapter.createView(row);
- row.addView(holder[i]);
+ final View v = mChooserListAdapter.createView(row);
+ v.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startSelected(holder.itemIndex, false, true);
+ }
+ });
+ v.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ showAppDetails(
+ mChooserListAdapter.resolveInfoForPosition(holder.itemIndex, true));
+ return true;
+ }
+ });
+ row.addView(v);
+ holder.cells[i] = v;
+
+ // Force height to be a given so we don't have visual disruption during scaling.
+ LayoutParams lp = v.getLayoutParams();
+ v.measure(spec, spec);
+ if (lp == null) {
+ lp = new LayoutParams(LayoutParams.MATCH_PARENT, v.getMeasuredHeight());
+ row.setLayoutParams(lp);
+ } else {
+ lp.height = v.getMeasuredHeight();
+ }
+ }
+
+ // Pre-measure so we can scale later.
+ holder.measure();
+ LayoutParams lp = row.getLayoutParams();
+ if (lp == null) {
+ lp = new LayoutParams(LayoutParams.MATCH_PARENT, holder.measuredRowHeight);
+ row.setLayoutParams(lp);
+ } else {
+ lp.height = holder.measuredRowHeight;
}
row.setTag(holder);
- holder[mColumnCount] = row;
return holder;
}
- void bindViewHolder(int rowPosition, View[] holder) {
+ void bindViewHolder(int rowPosition, RowViewHolder holder) {
final int start = getFirstRowPosition(rowPosition);
final int startType = mChooserListAdapter.getPositionTargetType(start);
@@ -994,34 +1147,26 @@ public class ChooserActivity extends ResolverActivity {
end--;
}
- final ViewGroup row = (ViewGroup) holder[mColumnCount];
-
if (startType == ChooserListAdapter.TARGET_SERVICE) {
- row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color));
+ holder.row.setBackgroundColor(
+ getColor(R.color.chooser_service_row_background_color));
} else {
- row.setBackground(null);
+ holder.row.setBackgroundColor(Color.TRANSPARENT);
+ }
+
+ final int oldHeight = holder.row.getLayoutParams().height;
+ holder.row.getLayoutParams().height = Math.max(1,
+ (int) (holder.measuredRowHeight * getRowScale(rowPosition)));
+ if (holder.row.getLayoutParams().height != oldHeight) {
+ holder.row.requestLayout();
}
for (int i = 0; i < mColumnCount; i++) {
- final View v = holder[i];
+ final View v = holder.cells[i];
if (start + i <= end) {
v.setVisibility(View.VISIBLE);
- final int itemIndex = start + i;
- mChooserListAdapter.bindView(itemIndex, v);
- v.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- startSelected(itemIndex, false, true);
- }
- });
- v.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- showAppDetails(
- mChooserListAdapter.resolveInfoForPosition(itemIndex, true));
- return true;
- }
- });
+ holder.itemIndex = start + i;
+ mChooserListAdapter.bindView(holder.itemIndex, v);
} else {
v.setVisibility(View.GONE);
}
@@ -1048,6 +1193,24 @@ public class ChooserActivity extends ResolverActivity {
}
}
+ static class RowViewHolder {
+ final View[] cells;
+ final ViewGroup row;
+ int measuredRowHeight;
+ int itemIndex;
+
+ public RowViewHolder(ViewGroup row, int cellCount) {
+ this.row = row;
+ this.cells = new View[cellCount];
+ }
+
+ public void measure() {
+ final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ row.measure(spec, spec);
+ measuredRowHeight = row.getMeasuredHeight();
+ }
+ }
+
static class ChooserTargetServiceConnection implements ServiceConnection {
private final DisplayResolveInfo mOriginalTarget;
private ComponentName mConnectedComponent;
@@ -1199,4 +1362,44 @@ public class ChooserActivity extends ResolverActivity {
mSelectedTarget = null;
}
}
+
+ class OffsetDataSetObserver extends DataSetObserver {
+ private final AbsListView mListView;
+ private int mCachedViewType = -1;
+ private View mCachedView;
+
+ public OffsetDataSetObserver(AbsListView listView) {
+ mListView = listView;
+ }
+
+ @Override
+ public void onChanged() {
+ if (mResolverDrawerLayout == null) {
+ return;
+ }
+
+ final int chooserTargetRows = mChooserRowAdapter.getServiceTargetRowCount();
+ int offset = 0;
+ for (int i = 0; i < chooserTargetRows; i++) {
+ final int pos = mChooserRowAdapter.getCallerTargetRowCount() + i;
+ final int vt = mChooserRowAdapter.getItemViewType(pos);
+ if (vt != mCachedViewType) {
+ mCachedView = null;
+ }
+ final View v = mChooserRowAdapter.getView(pos, mCachedView, mListView);
+ int height = ((RowViewHolder) (v.getTag())).measuredRowHeight;
+
+ offset += (int) (height * mChooserRowAdapter.getRowScale(pos) * chooserTargetRows);
+
+ if (vt >= 0) {
+ mCachedViewType = vt;
+ mCachedView = v;
+ } else {
+ mCachedViewType = -1;
+ }
+ }
+
+ mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
+ }
+ }
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 1710489..ba0912a 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -103,6 +103,8 @@ public class ResolverActivity extends Activity {
private ResolverComparator mResolverComparator;
private PickTargetOptionRequest mPickOptionRequest;
+ protected ResolverDrawerLayout mResolverDrawerLayout;
+
private boolean mRegistered;
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@Override public void onSomePackagesChanged() {
@@ -253,6 +255,7 @@ public class ResolverActivity extends Activity {
if (isVoiceInteraction()) {
rdl.setCollapsed(false);
}
+ mResolverDrawerLayout = rdl;
}
if (title == null) {
@@ -1570,7 +1573,10 @@ public class ResolverActivity extends Activity {
private void onBindView(View view, TargetInfo info) {
final ViewHolder holder = (ViewHolder) view.getTag();
- holder.text.setText(info.getDisplayLabel());
+ final CharSequence label = info.getDisplayLabel();
+ if (!TextUtils.equals(holder.text.getText(), label)) {
+ holder.text.setText(info.getDisplayLabel());
+ }
if (showsExtendedInfo(info)) {
holder.text2.setVisibility(View.VISIBLE);
holder.text2.setText(info.getExtendedInfo());
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 7679624..c4347f8 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -69,6 +69,12 @@ public class ResolverDrawerLayout extends ViewGroup {
private int mCollapsibleHeight;
private int mUncollapsibleHeight;
+ /**
+ * The height in pixels of reserved space added to the top of the collapsed UI;
+ * e.g. chooser targets
+ */
+ private int mCollapsibleHeightReserved;
+
private int mTopOffset;
private boolean mIsDragging;
@@ -153,12 +159,62 @@ public class ResolverDrawerLayout extends ViewGroup {
}
}
+ public void setCollapsibleHeightReserved(int heightPixels) {
+ final int oldReserved = mCollapsibleHeightReserved;
+ mCollapsibleHeightReserved = heightPixels;
+
+ final int dReserved = mCollapsibleHeightReserved - oldReserved;
+ if (dReserved != 0 && mIsDragging) {
+ mLastTouchY -= dReserved;
+ }
+
+ final int oldCollapsibleHeight = mCollapsibleHeight;
+ mCollapsibleHeight = Math.max(mCollapsibleHeight, getMaxCollapsedHeight());
+
+ if (updateCollapseOffset(oldCollapsibleHeight, !isDragging())) {
+ return;
+ }
+
+ invalidate();
+ }
+
private boolean isMoving() {
return mIsDragging || !mScroller.isFinished();
}
+ private boolean isDragging() {
+ return mIsDragging || getNestedScrollAxes() == SCROLL_AXIS_VERTICAL;
+ }
+
+ private boolean updateCollapseOffset(int oldCollapsibleHeight, boolean remainClosed) {
+ if (oldCollapsibleHeight == mCollapsibleHeight) {
+ return false;
+ }
+
+ if (isLaidOut()) {
+ final boolean isCollapsedOld = mCollapseOffset != 0;
+ if (remainClosed && (oldCollapsibleHeight < mCollapsibleHeight
+ && mCollapseOffset == oldCollapsibleHeight)) {
+ // Stay closed even at the new height.
+ mCollapseOffset = mCollapsibleHeight;
+ } else {
+ mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight);
+ }
+ final boolean isCollapsedNew = mCollapseOffset != 0;
+ if (isCollapsedOld != isCollapsedNew) {
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+ } else {
+ // Start out collapsed at first unless we restored state for otherwise
+ mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight;
+ }
+ return true;
+ }
+
private int getMaxCollapsedHeight() {
- return isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight;
+ return (isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight)
+ + mCollapsibleHeightReserved;
}
public void setOnDismissedListener(OnDismissedListener listener) {
@@ -676,7 +732,7 @@ public class ResolverDrawerLayout extends ViewGroup {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.alwaysShow && child.getVisibility() != GONE) {
measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed);
- heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin;
+ heightUsed += getHeightUsed(child);
}
}
@@ -688,7 +744,7 @@ public class ResolverDrawerLayout extends ViewGroup {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.alwaysShow && child.getVisibility() != GONE) {
measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed);
- heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin;
+ heightUsed += getHeightUsed(child);
}
}
@@ -697,30 +753,43 @@ public class ResolverDrawerLayout extends ViewGroup {
heightUsed - alwaysShowHeight - getMaxCollapsedHeight());
mUncollapsibleHeight = heightUsed - mCollapsibleHeight;
- if (isLaidOut()) {
- final boolean isCollapsedOld = mCollapseOffset != 0;
- if (oldCollapsibleHeight < mCollapsibleHeight
- && mCollapseOffset == oldCollapsibleHeight) {
- // Stay closed even at the new height.
- mCollapseOffset = mCollapsibleHeight;
- } else {
- mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight);
- }
- final boolean isCollapsedNew = mCollapseOffset != 0;
- if (isCollapsedOld != isCollapsedNew) {
- notifyViewAccessibilityStateChangedIfNeeded(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
- }
- } else {
- // Start out collapsed at first unless we restored state for otherwise
- mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight;
- }
+ updateCollapseOffset(oldCollapsibleHeight, !isDragging());
mTopOffset = Math.max(0, heightSize - heightUsed) + (int) mCollapseOffset;
setMeasuredDimension(sourceWidth, heightSize);
}
+ private int getHeightUsed(View child) {
+ // This method exists because we're taking a fast path at measuring ListViews that
+ // lets us get away with not doing the more expensive wrap_content measurement which
+ // imposes double child view measurement costs. If we're looking at a ListView, we can
+ // check against the lowest child view plus padding and margin instead of the actual
+ // measured height of the ListView. This lets the ListView hang off the edge when
+ // all of the content would fit on-screen.
+
+ int heightUsed = child.getMeasuredHeight();
+ if (child instanceof AbsListView) {
+ final AbsListView lv = (AbsListView) child;
+ final int lvPaddingBottom = lv.getPaddingBottom();
+
+ int lowest = 0;
+ for (int i = 0, N = lv.getChildCount(); i < N; i++) {
+ final int bottom = lv.getChildAt(i).getBottom() + lvPaddingBottom;
+ if (bottom > lowest) {
+ lowest = bottom;
+ }
+ }
+
+ if (lowest < heightUsed) {
+ heightUsed = lowest;
+ }
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ return lp.topMargin + heightUsed + lp.bottomMargin;
+ }
+
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = getWidth();