diff options
author | Jeff Sharkey <jsharkey@android.com> | 2013-09-18 16:26:49 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2013-09-18 17:12:25 -0700 |
commit | e20a3acdc2d52c7eeb76940206145b3c419394a6 (patch) | |
tree | 450a27e9b12d402241b4c403934319487d86bc26 /packages | |
parent | 6df7d4a574ffd85c82cad402552e3854df3a3f85 (diff) | |
download | frameworks_base-e20a3acdc2d52c7eeb76940206145b3c419394a6.zip frameworks_base-e20a3acdc2d52c7eeb76940206145b3c419394a6.tar.gz frameworks_base-e20a3acdc2d52c7eeb76940206145b3c419394a6.tar.bz2 |
Save directory state and animate.
Save scroll position and restore when rotating or going up the
directory stack. Also show directory animations when navigating
the directory stack.
Bug: 10417201
Change-Id: Ia2c508debc2bffffe6306eb9078afefef259dfe2
Diffstat (limited to 'packages')
10 files changed, 290 insertions, 29 deletions
diff --git a/packages/DocumentsUI/res/animator/dir_down.xml b/packages/DocumentsUI/res/animator/dir_down.xml new file mode 100644 index 0000000..7f547f1 --- /dev/null +++ b/packages/DocumentsUI/res/animator/dir_down.xml @@ -0,0 +1,22 @@ +<!-- Copyright (C) 2013 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:valueFrom="1" + android:valueTo="0" + android:propertyName="position" + android:valueType="floatType" + android:duration="@android:integer/config_mediumAnimTime" + android:interpolator="@android:interpolator/decelerate_quad" /> diff --git a/packages/DocumentsUI/res/animator/dir_frozen.xml b/packages/DocumentsUI/res/animator/dir_frozen.xml new file mode 100644 index 0000000..b541d13 --- /dev/null +++ b/packages/DocumentsUI/res/animator/dir_frozen.xml @@ -0,0 +1,21 @@ +<!-- Copyright (C) 2013 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:valueFrom="0" + android:valueTo="0" + android:propertyName="position" + android:valueType="floatType" + android:duration="@android:integer/config_mediumAnimTime" /> diff --git a/packages/DocumentsUI/res/animator/dir_up.xml b/packages/DocumentsUI/res/animator/dir_up.xml new file mode 100644 index 0000000..fda0faf --- /dev/null +++ b/packages/DocumentsUI/res/animator/dir_up.xml @@ -0,0 +1,22 @@ +<!-- Copyright (C) 2013 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:valueFrom="0" + android:valueTo="1" + android:propertyName="position" + android:valueType="floatType" + android:duration="@android:integer/config_mediumAnimTime" + android:interpolator="@android:interpolator/accelerate_quad" /> diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow.9.png Binary files differnew file mode 100644 index 0000000..0240874 --- /dev/null +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow.9.png diff --git a/packages/DocumentsUI/res/layout/activity.xml b/packages/DocumentsUI/res/layout/activity.xml index ff28e41..9937c39 100644 --- a/packages/DocumentsUI/res/layout/activity.xml +++ b/packages/DocumentsUI/res/layout/activity.xml @@ -24,7 +24,7 @@ android:layout_height="match_parent" android:orientation="vertical"> - <FrameLayout + <com.android.documentsui.DirectoryContainerView android:id="@+id/container_directory" android:layout_width="match_parent" android:layout_height="0dip" diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml index b4138a5..07bf127 100644 --- a/packages/DocumentsUI/res/layout/fragment_directory.xml +++ b/packages/DocumentsUI/res/layout/fragment_directory.xml @@ -14,9 +14,10 @@ limitations under the License. --> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" +<com.android.documentsui.DirectoryView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:background="@drawable/ic_dir_shadow"> <TextView android:id="@android:id/empty" @@ -40,4 +41,4 @@ android:listSelector="@android:color/transparent" android:visibility="gone" /> -</FrameLayout> +</com.android.documentsui.DirectoryView> diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryContainerView.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryContainerView.java new file mode 100644 index 0000000..77595b6 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryContainerView.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui; + +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; + +import java.util.ArrayList; + +public class DirectoryContainerView extends FrameLayout { + private boolean mDisappearingFirst = false; + + public DirectoryContainerView(Context context) { + super(context); + setClipChildren(false); + } + + public DirectoryContainerView(Context context, AttributeSet attrs) { + super(context, attrs); + setClipChildren(false); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + final ArrayList<View> disappearing = mDisappearingChildren; + if (mDisappearingFirst && disappearing != null) { + for (int i = 0; i < disappearing.size(); i++) { + super.drawChild(canvas, disappearing.get(i), getDrawingTime()); + } + } + super.dispatchDraw(canvas); + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + if (mDisappearingFirst && mDisappearingChildren != null + && mDisappearingChildren.contains(child)) { + return false; + } + return super.drawChild(canvas, child, drawingTime); + } + + public void setDrawDisappearingFirst(boolean disappearingFirst) { + mDisappearingFirst = disappearingFirst; + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 198927c..5b6ec4d 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -43,12 +43,14 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.CancellationSignal; +import android.os.Parcelable; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.text.format.DateUtils; import android.text.format.Formatter; import android.text.format.Time; import android.util.Log; +import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.ActionMode; import android.view.LayoutInflater; @@ -96,7 +98,13 @@ public class DirectoryFragment extends Fragment { public static final int TYPE_SEARCH = 2; public static final int TYPE_RECENT_OPEN = 3; + public static final int ANIM_NONE = 1; + public static final int ANIM_SIDE = 2; + public static final int ANIM_DOWN = 3; + public static final int ANIM_UP = 4; + private int mType = TYPE_NORMAL; + private String mStateKey; private int mLastMode = MODE_UNKNOWN; private int mLastSortOrder = SORT_ORDER_UNKNOWN; @@ -113,39 +121,61 @@ public class DirectoryFragment extends Fragment { private static final String EXTRA_ROOT = "root"; private static final String EXTRA_DOC = "doc"; private static final String EXTRA_QUERY = "query"; + private static final String EXTRA_IGNORE_STATE = "ignoreState"; private static AtomicInteger sLoaderId = new AtomicInteger(4000); private final int mLoaderId = sLoaderId.incrementAndGet(); - public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc) { - show(fm, TYPE_NORMAL, root, doc, null); + public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) { + show(fm, TYPE_NORMAL, root, doc, null, anim); } - public static void showSearch(FragmentManager fm, RootInfo root, String query) { - show(fm, TYPE_SEARCH, root, null, query); + public static void showSearch(FragmentManager fm, RootInfo root, String query, int anim) { + show(fm, TYPE_SEARCH, root, null, query, anim); } - public static void showRecentsOpen(FragmentManager fm) { - show(fm, TYPE_RECENT_OPEN, null, null, null); + public static void showRecentsOpen(FragmentManager fm, int anim) { + show(fm, TYPE_RECENT_OPEN, null, null, null, anim); } - private static void show( - FragmentManager fm, int type, RootInfo root, DocumentInfo doc, String query) { + private static void show(FragmentManager fm, int type, RootInfo root, DocumentInfo doc, + String query, int anim) { final Bundle args = new Bundle(); args.putInt(EXTRA_TYPE, type); args.putParcelable(EXTRA_ROOT, root); args.putParcelable(EXTRA_DOC, doc); args.putString(EXTRA_QUERY, query); + final FragmentTransaction ft = fm.beginTransaction(); + switch (anim) { + case ANIM_SIDE: + args.putBoolean(EXTRA_IGNORE_STATE, true); + break; + case ANIM_DOWN: + args.putBoolean(EXTRA_IGNORE_STATE, true); + ft.setCustomAnimations(R.animator.dir_down, R.animator.dir_frozen); + break; + case ANIM_UP: + ft.setCustomAnimations(R.animator.dir_frozen, R.animator.dir_up); + break; + } + final DirectoryFragment fragment = new DirectoryFragment(); fragment.setArguments(args); - final FragmentTransaction ft = fm.beginTransaction(); ft.replace(R.id.container_directory, fragment); ft.commitAllowingStateLoss(); } + private static String buildStateKey(RootInfo root, DocumentInfo doc) { + final StringBuilder builder = new StringBuilder(); + builder.append(root != null ? root.authority : "null").append(';'); + builder.append(root != null ? root.rootId : "null").append(';'); + builder.append(doc != null ? doc.documentId : "null"); + return builder.toString(); + } + public static DirectoryFragment get(FragmentManager fm) { // TODO: deal with multiple directories shown at once return (DirectoryFragment) fm.findFragmentById(R.id.container_directory); @@ -184,6 +214,7 @@ public class DirectoryFragment extends Fragment { mAdapter = new DocumentsAdapter(); mType = getArguments().getInt(EXTRA_TYPE); + mStateKey = buildStateKey(root, doc); if (mType == TYPE_RECENT_OPEN) { // Hide titles when showing recents for picking images/videos @@ -241,11 +272,16 @@ public class DirectoryFragment extends Fragment { updateDisplayState(); - if (mLastSortOrder != state.derivedSortOrder) { - mLastSortOrder = state.derivedSortOrder; + // Restore any previous instance state + final SparseArray<Parcelable> container = state.dirState.remove(mStateKey); + if (container != null && !getArguments().getBoolean(EXTRA_IGNORE_STATE, false)) { + getView().restoreHierarchyState(container); + } else if (mLastSortOrder != state.derivedSortOrder) { mListView.smoothScrollToPosition(0); mGridView.smoothScrollToPosition(0); } + + mLastSortOrder = state.derivedSortOrder; } @Override @@ -261,6 +297,17 @@ public class DirectoryFragment extends Fragment { } @Override + public void onStop() { + super.onStop(); + + // Remember last scroll location + final SparseArray<Parcelable> container = new SparseArray<Parcelable>(); + getView().saveHierarchyState(container); + final State state = getDisplayState(this); + state.dirState.put(mStateKey, container); + } + + @Override public void onResume() { super.onResume(); updateDisplayState(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryView.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryView.java new file mode 100644 index 0000000..34cb14f --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryView.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui; + +import android.content.Context; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +public class DirectoryView extends FrameLayout { + private float mPosition = 0f; + + private int mWidth; + + public DirectoryView(Context context) { + super(context); + } + + public DirectoryView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setBackground(Drawable background) { + final Rect rect = new Rect(); + background.getPadding(rect); + final InsetDrawable inset = new InsetDrawable(background, -rect.left, 0, 0, 0); + super.setBackground(inset); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mWidth = w; + setPosition(mPosition); + } + + public float getPosition() { + return mPosition; + } + + public void setPosition(float position) { + mPosition = position; + setX((mWidth > 0) ? (mPosition * mWidth) : 0); + setAlpha(1f - position); + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index f6cb481..3b71f60 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -16,6 +16,10 @@ package com.android.documentsui; +import static com.android.documentsui.DirectoryFragment.ANIM_DOWN; +import static com.android.documentsui.DirectoryFragment.ANIM_NONE; +import static com.android.documentsui.DirectoryFragment.ANIM_SIDE; +import static com.android.documentsui.DirectoryFragment.ANIM_UP; import static com.android.documentsui.DocumentsActivity.State.ACTION_CREATE; import static com.android.documentsui.DocumentsActivity.State.ACTION_GET_CONTENT; import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE; @@ -44,6 +48,7 @@ import android.graphics.drawable.InsetDrawable; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; +import android.os.Parcelable; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Root; import android.support.v4.app.ActionBarDrawerToggle; @@ -51,6 +56,7 @@ import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout.DrawerListener; import android.util.Log; +import android.util.SparseArray; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -73,12 +79,14 @@ import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentStack; import com.android.documentsui.model.DurableUtils; import com.android.documentsui.model.RootInfo; +import com.google.common.collect.Maps; import libcore.io.IoUtils; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; +import java.util.HashMap; import java.util.List; public class DocumentsActivity extends Activity { @@ -94,6 +102,8 @@ public class DocumentsActivity extends Activity { private ActionBarDrawerToggle mDrawerToggle; private View mRootsContainer; + private DirectoryContainerView mDirectoryContainer; + private boolean mIgnoreNextNavigation; private boolean mIgnoreNextClose; private boolean mIgnoreNextCollapse; @@ -165,6 +175,8 @@ public class DocumentsActivity extends Activity { mRootsContainer = findViewById(R.id.container_roots); } + mDirectoryContainer = (DirectoryContainerView) findViewById(R.id.container_directory); + if (icicle != null) { mState = icicle.getParcelable(EXTRA_STATE); } else { @@ -195,7 +207,7 @@ public class DocumentsActivity extends Activity { RootsFragment.show(getFragmentManager(), null); } - onCurrentDirectoryChanged(); + onCurrentDirectoryChanged(ANIM_NONE); } private void buildDefaultState() { @@ -397,7 +409,7 @@ public class DocumentsActivity extends Activity { public boolean onQueryTextSubmit(String query) { mState.currentSearch = query; mSearchView.clearFocus(); - onCurrentDirectoryChanged(); + onCurrentDirectoryChanged(ANIM_NONE); return true; } @@ -421,7 +433,7 @@ public class DocumentsActivity extends Activity { } mState.currentSearch = null; - onCurrentDirectoryChanged(); + onCurrentDirectoryChanged(ANIM_NONE); return true; } }); @@ -435,7 +447,7 @@ public class DocumentsActivity extends Activity { } mState.currentSearch = null; - onCurrentDirectoryChanged(); + onCurrentDirectoryChanged(ANIM_NONE); return false; } }); @@ -595,7 +607,7 @@ public class DocumentsActivity extends Activity { final int size = mState.stack.size(); if (size > 1) { mState.stack.pop(); - onCurrentDirectoryChanged(); + onCurrentDirectoryChanged(ANIM_UP); } else if (size == 1 && !isRootsDrawerOpen()) { // TODO: open root drawer once we can capture back key super.onBackPressed(); @@ -690,7 +702,7 @@ public class DocumentsActivity extends Activity { mState.stackTouched = true; mState.stack.pop(); } - onCurrentDirectoryChanged(); + onCurrentDirectoryChanged(ANIM_UP); return true; } }; @@ -711,17 +723,19 @@ public class DocumentsActivity extends Activity { return mState; } - private void onCurrentDirectoryChanged() { + private void onCurrentDirectoryChanged(int anim) { final FragmentManager fm = getFragmentManager(); final RootInfo root = getCurrentRoot(); final DocumentInfo cwd = getCurrentDirectory(); + mDirectoryContainer.setDrawDisappearingFirst(anim == ANIM_DOWN); + if (cwd == null) { // No directory means recents if (mState.action == ACTION_CREATE) { RecentsCreateFragment.show(fm); } else { - DirectoryFragment.showRecentsOpen(fm); + DirectoryFragment.showRecentsOpen(fm, anim); // Start recents in relevant mode final boolean acceptImages = MimePredicate.mimeMatches( @@ -732,10 +746,10 @@ public class DocumentsActivity extends Activity { } else { if (mState.currentSearch != null) { // Ongoing search - DirectoryFragment.showSearch(fm, root, mState.currentSearch); + DirectoryFragment.showSearch(fm, root, mState.currentSearch, anim); } else { // Normal boring directory - DirectoryFragment.showNormal(fm, root, cwd); + DirectoryFragment.showNormal(fm, root, cwd, anim); } } @@ -760,7 +774,7 @@ public class DocumentsActivity extends Activity { public void onStackPicked(DocumentStack stack) { mState.stack = stack; mState.stackTouched = true; - onCurrentDirectoryChanged(); + onCurrentDirectoryChanged(ANIM_SIDE); } public void onRootPicked(RootInfo root, boolean closeDrawer) { @@ -772,11 +786,14 @@ public class DocumentsActivity extends Activity { if (!mRoots.isRecentsRoot(root)) { try { final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.documentId); - onDocumentPicked(DocumentInfo.fromUri(getContentResolver(), uri)); + final DocumentInfo doc = DocumentInfo.fromUri(getContentResolver(), uri); + mState.stack.push(doc); + mState.stackTouched = true; + onCurrentDirectoryChanged(ANIM_SIDE); } catch (FileNotFoundException e) { } } else { - onCurrentDirectoryChanged(); + onCurrentDirectoryChanged(ANIM_SIDE); } if (closeDrawer) { @@ -798,7 +815,7 @@ public class DocumentsActivity extends Activity { if (doc.isDirectory()) { mState.stack.push(doc); mState.stackTouched = true; - onCurrentDirectoryChanged(); + onCurrentDirectoryChanged(ANIM_DOWN); } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { // Explicit file picked, return onFinished(doc.derivedUri); @@ -924,6 +941,9 @@ public class DocumentsActivity extends Activity { /** Currently active search, overriding any stack. */ public String currentSearch; + /** Instance state for every shown directory */ + public HashMap<String, SparseArray<Parcelable>> dirState = Maps.newHashMap(); + public static final int ACTION_OPEN = 1; public static final int ACTION_CREATE = 2; public static final int ACTION_GET_CONTENT = 3; @@ -956,6 +976,7 @@ public class DocumentsActivity extends Activity { out.writeInt(stackTouched ? 1 : 0); DurableUtils.writeToParcel(out, stack); out.writeString(currentSearch); + out.writeMap(dirState); } public static final Creator<State> CREATOR = new Creator<State>() { @@ -973,6 +994,7 @@ public class DocumentsActivity extends Activity { state.stackTouched = in.readInt() != 0; DurableUtils.readFromParcel(in, state.stack); state.currentSearch = in.readString(); + in.readMap(state.dirState, null); return state; } |