summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDianne Hackborn <hackbod@google.com>2010-06-25 15:52:59 -0700
committerDianne Hackborn <hackbod@google.com>2010-06-28 15:17:44 -0700
commit445646c52128a763b56ed7bb3bd009e2f33e3e4f (patch)
treebb1c99d5c7e8c5fd655c1050281717c37554498b
parent623e78b79259f00b3670ce6dbe1f13eedf5acee4 (diff)
downloadframeworks_base-445646c52128a763b56ed7bb3bd009e2f33e3e4f.zip
frameworks_base-445646c52128a763b56ed7bb3bd009e2f33e3e4f.tar.gz
frameworks_base-445646c52128a763b56ed7bb3bd009e2f33e3e4f.tar.bz2
Improvements to ListFragment.
Now deals correctly with a content view containing just a list, and adds a lot more built-in functionality: ability to show custom text for an empty list, and indeterminant progress while populating the list. In addition, reworks transaction committing to be more aggressive about committing the transactions as the containing activity moves between its states (rather than waiting for the activity's handler to process the transaction message whenever that may finally happen). And fixed a bug with saving/restoring state of transaction replace operations. Change-Id: I9617a0c4f248b50a61b319910323639b6de24f73
-rw-r--r--api/current.xml34
-rw-r--r--core/java/android/app/Activity.java4
-rw-r--r--core/java/android/app/BackStackEntry.java37
-rw-r--r--core/java/android/app/FragmentManager.java63
-rw-r--r--core/java/android/app/ListFragment.java108
-rw-r--r--core/java/android/app/LoaderManagingFragment.java2
-rw-r--r--core/res/res/layout/list_content_rich.xml56
-rw-r--r--core/res/res/values/strings.xml3
8 files changed, 288 insertions, 19 deletions
diff --git a/api/current.xml b/api/current.xml
index 50546d3..dc4ba2c 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -28030,6 +28030,19 @@
<parameter name="id" type="long">
</parameter>
</method>
+<method name="setEmptyText"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="text" type="java.lang.CharSequence">
+</parameter>
+</method>
<method name="setListAdapter"
return="void"
abstract="false"
@@ -28043,6 +28056,21 @@
<parameter name="adapter" type="android.widget.ListAdapter">
</parameter>
</method>
+<method name="setListShown"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="shown" type="boolean">
+</parameter>
+<parameter name="animate" type="boolean">
+</parameter>
+</method>
<method name="setSelection"
return="void"
abstract="false"
@@ -28152,7 +28180,7 @@
static="false"
final="false"
deprecated="not deprecated"
- visibility="protected"
+ visibility="public"
>
<parameter name="id" type="int">
</parameter>
@@ -223896,7 +223924,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="t" type="T">
+<parameter name="arg0" type="T">
</parameter>
</method>
</interface>
@@ -247337,7 +247365,7 @@
<method name="availableProcessors"
return="int"
abstract="false"
- native="false"
+ native="true"
synchronized="false"
static="false"
final="false"
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4a47b66..91ff0a5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4036,6 +4036,7 @@ public class Activity extends ContextThemeWrapper
final void performStart() {
mCalled = false;
+ mFragments.execPendingActions();
mInstrumentation.callActivityOnStart(this);
if (!mCalled) {
throw new SuperNotCalledException(
@@ -4074,6 +4075,8 @@ public class Activity extends ContextThemeWrapper
final void performResume() {
performRestart();
+ mFragments.execPendingActions();
+
mLastNonConfigurationInstances = null;
// First call onResume() -before- setting mResumed, so we don't
@@ -4091,6 +4094,7 @@ public class Activity extends ContextThemeWrapper
mCalled = false;
mFragments.dispatchResume();
+ mFragments.execPendingActions();
onPostResume();
if (!mCalled) {
diff --git a/core/java/android/app/BackStackEntry.java b/core/java/android/app/BackStackEntry.java
index 5e9aea5..c958e26 100644
--- a/core/java/android/app/BackStackEntry.java
+++ b/core/java/android/app/BackStackEntry.java
@@ -18,6 +18,7 @@ package android.app;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
import java.util.ArrayList;
@@ -28,14 +29,30 @@ final class BackStackState implements Parcelable {
final String mName;
public BackStackState(FragmentManager fm, BackStackEntry bse) {
- mOps = new int[bse.mNumOp*4];
+ int numRemoved = 0;
BackStackEntry.Op op = bse.mHead;
+ while (op != null) {
+ if (op.removed != null) numRemoved += op.removed.size();
+ op = op.next;
+ }
+ mOps = new int[bse.mNumOp*5 + numRemoved];
+
+ op = bse.mHead;
int pos = 0;
while (op != null) {
mOps[pos++] = op.cmd;
mOps[pos++] = op.fragment.mIndex;
mOps[pos++] = op.enterAnim;
mOps[pos++] = op.exitAnim;
+ if (op.removed != null) {
+ final int N = op.removed.size();
+ mOps[pos++] = N;
+ for (int i=0; i<N; i++) {
+ mOps[pos++] = op.removed.get(i).mIndex;
+ }
+ } else {
+ mOps[pos++] = 0;
+ }
op = op.next;
}
mTransition = bse.mTransition;
@@ -61,6 +78,13 @@ final class BackStackState implements Parcelable {
op.fragment = f;
op.enterAnim = mOps[pos++];
op.exitAnim = mOps[pos++];
+ final int N = mOps[pos++];
+ if (N > 0) {
+ op.removed = new ArrayList<Fragment>(N);
+ for (int i=0; i<N; i++) {
+ op.removed.add(fm.mActive.get(mOps[pos++]));
+ }
+ }
bse.addOp(op);
}
bse.mTransition = mTransition;
@@ -96,6 +120,8 @@ final class BackStackState implements Parcelable {
* @hide Entry of an operation on the fragment back stack.
*/
final class BackStackEntry implements FragmentTransaction, Runnable {
+ static final String TAG = "BackStackEntry";
+
final FragmentManager mManager;
static final int OP_NULL = 0;
@@ -265,11 +291,14 @@ final class BackStackEntry implements FragmentTransaction, Runnable {
public void commit() {
if (mCommitted) throw new IllegalStateException("commit already called");
+ if (FragmentManager.DEBUG) Log.v(TAG, "Commit: " + this);
mCommitted = true;
- mManager.mActivity.mHandler.post(this);
+ mManager.enqueueAction(this);
}
public void run() {
+ if (FragmentManager.DEBUG) Log.v(TAG, "Run: " + this);
+
Op op = mHead;
while (op != null) {
switch (op.cmd) {
@@ -286,6 +315,8 @@ final class BackStackEntry implements FragmentTransaction, Runnable {
if (mManager.mAdded != null) {
for (int i=0; i<mManager.mAdded.size(); i++) {
Fragment old = mManager.mAdded.get(i);
+ if (FragmentManager.DEBUG) Log.v(TAG,
+ "OP_REPLACE: adding=" + f + " old=" + old);
if (old.mContainerId == f.mContainerId) {
if (op.removed == null) {
op.removed = new ArrayList<Fragment>();
@@ -350,6 +381,8 @@ final class BackStackEntry implements FragmentTransaction, Runnable {
}
public void popFromBackStack() {
+ if (FragmentManager.DEBUG) Log.v(TAG, "popFromBackStack: " + this);
+
Op op = mTail;
while (op != null) {
switch (op.cmd) {
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index c0ab0b5..b8eeb09 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -77,6 +77,10 @@ public class FragmentManager {
static final boolean DEBUG = true;
static final String TAG = "FragmentManager";
+ ArrayList<Runnable> mPendingActions;
+ Runnable[] mTmpActions;
+ boolean mExecutingActions;
+
ArrayList<Fragment> mActive;
ArrayList<Fragment> mAdded;
ArrayList<Integer> mAvailIndices;
@@ -91,6 +95,13 @@ public class FragmentManager {
Bundle mStateBundle = null;
SparseArray<Parcelable> mStateArray = null;
+ Runnable mExecCommit = new Runnable() {
+ @Override
+ public void run() {
+ execPendingActions();
+ }
+ };
+
Animation loadAnimation(Fragment fragment, int transit, boolean enter,
int transitionStyle) {
Animation animObj = fragment.onCreateAnimation(transitionStyle, enter,
@@ -486,6 +497,52 @@ public class FragmentManager {
return null;
}
+ public void enqueueAction(Runnable action) {
+ synchronized (this) {
+ if (mPendingActions == null) {
+ mPendingActions = new ArrayList<Runnable>();
+ }
+ mPendingActions.add(action);
+ if (mPendingActions.size() == 1) {
+ mActivity.mHandler.removeCallbacks(mExecCommit);
+ mActivity.mHandler.post(mExecCommit);
+ }
+ }
+ }
+
+ /**
+ * Only call from main thread!
+ */
+ public void execPendingActions() {
+ if (mExecutingActions) {
+ throw new IllegalStateException("Recursive entry to execPendingActions");
+ }
+
+ while (true) {
+ int numActions;
+
+ synchronized (this) {
+ if (mPendingActions == null || mPendingActions.size() == 0) {
+ return;
+ }
+
+ numActions = mPendingActions.size();
+ if (mTmpActions == null || mTmpActions.length < numActions) {
+ mTmpActions = new Runnable[numActions];
+ }
+ mPendingActions.toArray(mTmpActions);
+ mPendingActions.clear();
+ mActivity.mHandler.removeCallbacks(mExecCommit);
+ }
+
+ mExecutingActions = true;
+ for (int i=0; i<numActions; i++) {
+ mTmpActions[i].run();
+ }
+ mExecutingActions = false;
+ }
+ }
+
public void addBackStackState(BackStackEntry state) {
if (mBackStack == null) {
mBackStack = new ArrayList<BackStackEntry>();
@@ -503,8 +560,9 @@ public class FragmentManager {
return false;
}
final BackStackEntry bss = mBackStack.remove(last);
- handler.post(new Runnable() {
+ enqueueAction(new Runnable() {
public void run() {
+ if (DEBUG) Log.v(TAG, "Popping back stack state: " + bss);
bss.popFromBackStack();
moveToState(mCurState, reverseTransit(bss.getTransition()),
bss.getTransitionStyle(), true);
@@ -526,9 +584,10 @@ public class FragmentManager {
for (int i=mBackStack.size()-1; i>index; i--) {
states.add(mBackStack.remove(i));
}
- handler.post(new Runnable() {
+ enqueueAction(new Runnable() {
public void run() {
for (int i=0; i<states.size(); i++) {
+ if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i));
states.get(i).popFromBackStack();
}
moveToState(mCurState, true);
diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java
index 0d8ec98..548dd61 100644
--- a/core/java/android/app/ListFragment.java
+++ b/core/java/android/app/ListFragment.java
@@ -21,9 +21,11 @@ import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
+import android.widget.TextView;
/**
* An fragment that displays a list of items by binding to a data source such as
@@ -154,17 +156,28 @@ public class ListFragment extends Fragment {
ListAdapter mAdapter;
ListView mList;
+ View mEmptyView;
+ TextView mStandardEmptyView;
+ View mProgressContainer;
+ View mListContainer;
+ boolean mSetEmptyView;
+ boolean mListShown;
public ListFragment() {
}
/**
- * Provide default implementation to return a simple list view.
+ * Provide default implementation to return a simple list view. Subclasses
+ * can override to replace with their own layout. If doing so, the
+ * returned view hierarchy <em>must</em> have a ListView whose id
+ * is {@link android.R.id.list android.R.id.list} and can optionally
+ * have a sibling view id {@link android.R.id.empty android.R.id.empty}
+ * that is to be shown when the list is empty.
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- return inflater.inflate(com.android.internal.R.layout.list_content,
+ return inflater.inflate(com.android.internal.R.layout.list_content_rich,
container, false);
}
@@ -247,6 +260,62 @@ public class ListFragment extends Fragment {
}
/**
+ * The default content for a ListFragment has a TextView that can
+ * be shown when the list is empty. If you would like to have it
+ * shown, call this method to supply the text it should use.
+ */
+ public void setEmptyText(CharSequence text) {
+ ensureList();
+ if (mStandardEmptyView == null) {
+ throw new IllegalStateException("Can't be used with a custom content view");
+ }
+ if (!mSetEmptyView) {
+ mSetEmptyView = true;
+ mList.setEmptyView(mStandardEmptyView);
+ }
+ }
+
+ /**
+ * Control whether the list is being displayed. You can make it not
+ * displayed if you are waiting for the initial data to show in it. During
+ * this time an indeterminant progress indicator will be shown instead.
+ *
+ * @param shown If true, the list view is shown; if false, the progress
+ * indicator. The initial value is true.
+ * @param animate If true, an animation will be used to transition to the
+ * new state.
+ */
+ public void setListShown(boolean shown, boolean animate) {
+ ensureList();
+ if (mProgressContainer == null) {
+ throw new IllegalStateException("Can't be used with a custom content view");
+ }
+ if (mListShown == shown) {
+ return;
+ }
+ mListShown = shown;
+ if (shown) {
+ if (animate) {
+ mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
+ getActivity(), android.R.anim.fade_out));
+ mListContainer.startAnimation(AnimationUtils.loadAnimation(
+ getActivity(), android.R.anim.fade_in));
+ }
+ mProgressContainer.setVisibility(View.GONE);
+ mListContainer.setVisibility(View.VISIBLE);
+ } else {
+ if (animate) {
+ mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
+ getActivity(), android.R.anim.fade_in));
+ mListContainer.startAnimation(AnimationUtils.loadAnimation(
+ getActivity(), android.R.anim.fade_out));
+ }
+ mProgressContainer.setVisibility(View.VISIBLE);
+ mListContainer.setVisibility(View.GONE);
+ }
+ }
+
+ /**
* Get the ListAdapter associated with this activity's ListView.
*/
public ListAdapter getListAdapter() {
@@ -261,16 +330,33 @@ public class ListFragment extends Fragment {
if (root == null) {
throw new IllegalStateException("Content view not yet created");
}
- View emptyView = root.findViewById(com.android.internal.R.id.empty);
- mList = (ListView)root.findViewById(com.android.internal.R.id.list);
- if (mList == null) {
- throw new RuntimeException(
- "Your content must have a ListView whose id attribute is " +
- "'android.R.id.list'");
- }
- if (emptyView != null) {
- mList.setEmptyView(emptyView);
+ if (root instanceof ListView) {
+ mList = (ListView)root;
+ } else {
+ mStandardEmptyView = (TextView)root.findViewById(
+ com.android.internal.R.id.internalEmpty);
+ if (mStandardEmptyView == null) {
+ mEmptyView = root.findViewById(android.R.id.empty);
+ }
+ mProgressContainer = root.findViewById(com.android.internal.R.id.progressContainer);
+ mListContainer = root.findViewById(com.android.internal.R.id.listContainer);
+ View rawListView = root.findViewById(android.R.id.list);
+ if (!(rawListView instanceof ListView)) {
+ throw new RuntimeException(
+ "Content has view with id attribute 'android.R.id.list' "
+ + "that is not a ListView class");
+ }
+ mList = (ListView)rawListView;
+ if (mList == null) {
+ throw new RuntimeException(
+ "Your content must have a ListView whose id attribute is " +
+ "'android.R.id.list'");
+ }
+ if (mEmptyView != null) {
+ mList.setEmptyView(mEmptyView);
+ }
}
+ mListShown = true;
mList.setOnItemClickListener(mOnClickListener);
if (mAdapter != null) {
setListAdapter(mAdapter);
diff --git a/core/java/android/app/LoaderManagingFragment.java b/core/java/android/app/LoaderManagingFragment.java
index a8a5ca4..5d417a0 100644
--- a/core/java/android/app/LoaderManagingFragment.java
+++ b/core/java/android/app/LoaderManagingFragment.java
@@ -44,7 +44,7 @@ public abstract class LoaderManagingFragment<D> extends Fragment
* when the new loader completes it's work. The callback will be delivered before the old loader
* is destroyed.
*/
- protected Loader<D> startLoading(int id, Bundle args) {
+ public Loader<D> startLoading(int id, Bundle args) {
LoaderInfo<D> info = mLoaders.get(id);
if (info != null) {
// Keep track of the previous instance of this loader so we can destroy
diff --git a/core/res/res/layout/list_content_rich.xml b/core/res/res/layout/list_content_rich.xml
new file mode 100644
index 0000000..1414032
--- /dev/null
+++ b/core/res/res/layout/list_content_rich.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2010, 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.
+*/
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout android:id="@+id/progressContainer"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ android:gravity="center">
+
+ <ProgressBar style="?android:attr/progressBarStyleLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="@string/loading"
+ android:paddingTop="4dip"
+ android:singleLine="true" />
+
+ </LinearLayout>
+
+ <FrameLayout android:id="@+id/listContainer"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:drawSelectorOnTop="false" />
+ <TextView android:id="@+android:id/internalEmpty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:textAppearance="?android:attr/textAppearanceLarge" />
+ </FrameLayout>
+
+</FrameLayout>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6b371df..de1ea68 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1919,6 +1919,9 @@
combined with setIcon(android.R.drawable.ic_dialog_alert) -->
<string name="dialog_alert_title">Attention</string>
+ <!-- Text shown by list fragment when waiting for data to display. -->
+ <string name="loading">Loading...</string>
+
<!-- Default text for a button that can be toggled on and off. -->
<string name="capital_on">ON</string>
<!-- Default text for a button that can be toggled on and off. -->