diff options
3 files changed, 243 insertions, 16 deletions
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 45a890702c0f..1e6e897ac44e 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -68,7 +68,6 @@ import android.support.v7.widget.RecyclerView.LayoutManager; import android.support.v7.widget.RecyclerView.OnItemTouchListener; import android.support.v7.widget.RecyclerView.RecyclerListener; import android.support.v7.widget.RecyclerView.ViewHolder; -import android.support.v7.widget.SimpleItemAnimator; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.format.Formatter; @@ -163,6 +162,9 @@ public class DirectoryFragment extends Fragment { private MessageBar mMessageBar; private View mProgressBar; + private int mSelectedItemColor; + private int mDefaultItemColor; + public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) { show(fm, TYPE_NORMAL, root, doc, null, anim); } @@ -255,8 +257,7 @@ public class DirectoryFragment extends Fragment { } }); - // TODO: Restore transition animations. See b/24802917. - ((SimpleItemAnimator) mRecView.getItemAnimator()).setSupportsChangeAnimations(false); + mRecView.setItemAnimator(new DirectoryItemAnimator(getActivity())); // TODO: Add a divider between views (which might use RecyclerView.ItemDecoration). if (DEBUG_ENABLE_DND) { @@ -294,6 +295,13 @@ public class DirectoryFragment extends Fragment { mAdapter = new DocumentsAdapter(context); mRecView.setAdapter(mAdapter); + mDefaultItemColor = context.getResources().getColor(android.R.color.transparent); + // Get the accent color. + TypedValue selColor = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.colorAccent, selColor, true); + // Set the opacity to 10%. + mSelectedItemColor = (selColor.data & 0x00ffffff) | 0x16000000; + GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener() { @Override @@ -899,24 +907,26 @@ public class DirectoryFragment extends Fragment { // Provide a reference to the views for each data item // Complex data items may need more than one view per item, and // you provide access to all the views for a data item in a view holder - private static final class DocumentHolder + private final class DocumentHolder extends RecyclerView.ViewHolder implements View.OnKeyListener { - // each data item is just a string in this case - public View view; public String docId; // The stable document id. private ClickListener mClickListener; private View.OnKeyListener mKeyListener; public DocumentHolder(View view) { super(view); - this.view = view; // Setting this using android:focusable in the item layouts doesn't work for list items. // So we set it here. Note that touch mode focus is a separate issue - see // View.setFocusableInTouchMode and View.isInTouchMode for more info. - this.view.setFocusable(true); - this.view.setOnKeyListener(this); + view.setFocusable(true); + view.setOnKeyListener(this); + } + + public void setSelected(boolean selected) { + itemView.setActivated(selected); + itemView.setBackgroundColor(selected ? mSelectedItemColor : mDefaultItemColor); } @Override @@ -945,10 +955,10 @@ public class DirectoryFragment extends Fragment { checkState(mKeyListener == null); mKeyListener = listener; } + } - interface ClickListener { - public void onClick(DocumentHolder doc); - } + interface ClickListener { + public void onClick(DocumentHolder doc); } void showEmptyView() { @@ -1007,6 +1017,24 @@ public class DirectoryFragment extends Fragment { return holder; } + /** + * Deal with selection changed events by using a custom ItemAnimator that just changes the + * background color. This works around focus issues (otherwise items lose focus when their + * selection state changes) but also optimizes change animations for selection. + */ + @Override + public void onBindViewHolder(DocumentHolder holder, int position, List<Object> payload) { + final View itemView = holder.itemView; + + if (payload.contains(MultiSelectManager.SELECTION_CHANGED_MARKER)) { + final boolean selected = isSelected(position); + itemView.setActivated(selected); + return; + } else { + onBindViewHolder(holder, position); + } + } + @Override public void onBindViewHolder(DocumentHolder holder, int position) { @@ -1032,8 +1060,9 @@ public class DirectoryFragment extends Fragment { final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE); holder.docId = docId; - final View itemView = holder.view; - itemView.setActivated(isSelected(position)); + final View itemView = holder.itemView; + + holder.setSelected(isSelected(position)); final View line1 = itemView.findViewById(R.id.line1); final View line2 = itemView.findViewById(R.id.line2); @@ -2035,7 +2064,7 @@ public class DirectoryFragment extends Fragment { } } - private class ItemClickListener implements DocumentHolder.ClickListener { + private class ItemClickListener implements ClickListener { @Override public void onClick(DocumentHolder doc) { final int position = doc.getAdapterPosition(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java new file mode 100644 index 000000000000..0eb1ea55895e --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2015 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.documentsui; + +import android.animation.Animator; +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.support.v4.util.ArrayMap; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.RecyclerView; +import android.util.TypedValue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Performs change animations on Items in DirectoryFragment's RecyclerView. This class overrides + * the way selection animations are normally performed - instead of cross fading the old Item with a + * new Item, this class manually animates a background color change. This enables selected Items to + * correctly maintain focus. + */ +class DirectoryItemAnimator extends DefaultItemAnimator { + private final List<ColorAnimation> mPendingAnimations = new ArrayList<>(); + private final Map<RecyclerView.ViewHolder, ColorAnimation> mRunningAnimations = + new ArrayMap<>(); + private final Integer mDefaultColor; + private final Integer mSelectedColor; + + public DirectoryItemAnimator(Context context) { + mDefaultColor = context.getResources().getColor(android.R.color.transparent); + // Get the accent color. + TypedValue selColor = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.colorAccent, selColor, true); + // Set the opacity to 10%. + mSelectedColor = (selColor.data & 0x00ffffff) | 0x16000000; + } + + @Override + public void runPendingAnimations() { + super.runPendingAnimations(); + for (ColorAnimation anim: mPendingAnimations) { + anim.start(); + mRunningAnimations.put(anim.viewHolder, anim); + } + mPendingAnimations.clear(); + } + + @Override + public void endAnimation(RecyclerView.ViewHolder vh) { + super.endAnimation(vh); + + for (int i = mPendingAnimations.size() - 1; i >= 0; --i) { + ColorAnimation anim = mPendingAnimations.get(i); + if (anim.viewHolder == vh) { + mPendingAnimations.remove(i); + anim.end(); + } + } + + ColorAnimation anim = mRunningAnimations.get(vh); + if (anim != null) { + anim.cancel(); + } + } + + @Override + public ItemHolderInfo recordPreLayoutInformation( + RecyclerView.State state, + RecyclerView.ViewHolder viewHolder, + @AdapterChanges int changeFlags, + List<Object> payloads) { + ItemInfo info = (ItemInfo) super.recordPreLayoutInformation(state, + viewHolder, changeFlags, payloads); + info.isActivated = viewHolder.itemView.isActivated(); + return info; + } + + + @Override + public ItemHolderInfo recordPostLayoutInformation( + RecyclerView.State state, RecyclerView.ViewHolder viewHolder) { + ItemInfo info = (ItemInfo) super.recordPostLayoutInformation(state, + viewHolder); + info.isActivated = viewHolder.itemView.isActivated(); + return info; + } + + @Override + public boolean animateChange(final RecyclerView.ViewHolder oldHolder, + RecyclerView.ViewHolder newHolder, ItemHolderInfo preInfo, + ItemHolderInfo postInfo) { + if (oldHolder != newHolder) { + return super.animateChange(oldHolder, newHolder, preInfo, postInfo); + } + + ItemInfo pre = (ItemInfo)preInfo; + ItemInfo post = (ItemInfo)postInfo; + + if (pre.isActivated == post.isActivated) { + dispatchAnimationFinished(oldHolder); + return false; + } else { + Integer startColor = pre.isActivated ? mSelectedColor : mDefaultColor; + Integer endColor = post.isActivated ? mSelectedColor : mDefaultColor; + oldHolder.itemView.setBackgroundColor(startColor); + mPendingAnimations.add(new ColorAnimation(oldHolder, startColor, endColor)); + } + return true; + } + + @Override + public ItemHolderInfo obtainHolderInfo() { + return new ItemInfo(); + } + + @Override + public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder vh) { + return true; + } + + class ItemInfo extends DefaultItemAnimator.ItemHolderInfo { + boolean isActivated; + }; + + /** + * Animates changes in background color. + */ + class ColorAnimation + implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener { + ValueAnimator mValueAnimator; + final RecyclerView.ViewHolder viewHolder; + int mEndColor; + + public ColorAnimation(RecyclerView.ViewHolder vh, int startColor, int endColor) + { + viewHolder = vh; + mValueAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), startColor, endColor); + mValueAnimator.addUpdateListener(this); + mValueAnimator.addListener(this); + + mEndColor = endColor; + } + + public void start() { + mValueAnimator.start(); + } + + public void cancel() { + mValueAnimator.cancel(); + } + + public void end() { + mValueAnimator.end(); + } + + @Override + public void onAnimationUpdate(ValueAnimator animator) { + viewHolder.itemView.setBackgroundColor((Integer)animator.getAnimatedValue()); + } + + @Override + public void onAnimationEnd(Animator animator) { + viewHolder.itemView.setBackgroundColor(mEndColor); + mRunningAnimations.remove(viewHolder); + dispatchAnimationFinished(viewHolder); + } + + @Override + public void onAnimationStart(Animator animation) { + dispatchAnimationStarted(viewHolder); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }; +}; diff --git a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java index ef53d53c2f78..4fde6ff74fea 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java +++ b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java @@ -76,6 +76,9 @@ public final class MultiSelectManager implements View.OnKeyListener { private Adapter<?> mAdapter; private boolean mSingleSelect; + // Payloads for notifyItemChange to distinguish between selection and other events. + public static final String SELECTION_CHANGED_MARKER = "Selection-Changed"; + @Nullable private BandController mBandManager; /** @@ -460,7 +463,7 @@ public final class MultiSelectManager implements View.OnKeyListener { for (int i = lastListener; i > -1; i--) { mCallbacks.get(i).onItemStateChanged(position, selected); } - mAdapter.notifyItemChanged(position); + mAdapter.notifyItemChanged(position, SELECTION_CHANGED_MARKER); } /** |