Loading packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +44 −15 Original line number Diff line number Diff line Loading @@ -66,7 +66,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; Loading Loading @@ -162,6 +161,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); } Loading Loading @@ -254,8 +256,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) { Loading Loading @@ -293,6 +294,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 Loading Loading @@ -897,24 +905,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 Loading Loading @@ -943,11 +953,11 @@ public class DirectoryFragment extends Fragment { checkState(mKeyListener == null); mKeyListener = listener; } } interface ClickListener { public void onClick(DocumentHolder doc); } } void showEmptyView() { mEmptyView.setVisibility(View.VISIBLE); Loading Loading @@ -1005,6 +1015,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) { Loading @@ -1030,8 +1058,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); Loading Loading @@ -1959,7 +1988,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(); Loading packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java 0 → 100644 +195 −0 Original line number Diff line number Diff line /* * 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) {} }; }; packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java +4 −1 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -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); } /** Loading Loading
packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +44 −15 Original line number Diff line number Diff line Loading @@ -66,7 +66,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; Loading Loading @@ -162,6 +161,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); } Loading Loading @@ -254,8 +256,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) { Loading Loading @@ -293,6 +294,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 Loading Loading @@ -897,24 +905,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 Loading Loading @@ -943,11 +953,11 @@ public class DirectoryFragment extends Fragment { checkState(mKeyListener == null); mKeyListener = listener; } } interface ClickListener { public void onClick(DocumentHolder doc); } } void showEmptyView() { mEmptyView.setVisibility(View.VISIBLE); Loading Loading @@ -1005,6 +1015,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) { Loading @@ -1030,8 +1058,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); Loading Loading @@ -1959,7 +1988,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(); Loading
packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java 0 → 100644 +195 −0 Original line number Diff line number Diff line /* * 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) {} }; };
packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java +4 −1 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -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); } /** Loading