Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.fragment.app;
     18 
     19 import android.content.Context;
     20 import android.os.Bundle;
     21 import android.os.Handler;
     22 import android.view.Gravity;
     23 import android.view.LayoutInflater;
     24 import android.view.View;
     25 import android.view.ViewGroup;
     26 import android.view.animation.AnimationUtils;
     27 import android.widget.AdapterView;
     28 import android.widget.FrameLayout;
     29 import android.widget.LinearLayout;
     30 import android.widget.ListAdapter;
     31 import android.widget.ListView;
     32 import android.widget.ProgressBar;
     33 import android.widget.TextView;
     34 
     35 import androidx.annotation.NonNull;
     36 import androidx.annotation.Nullable;
     37 
     38 /**
     39  * Static library support version of the framework's {@link android.app.ListFragment}.
     40  * Used to write apps that run on platforms prior to Android 3.0.  When running
     41  * on Android 3.0 or above, this implementation is still used; it does not try
     42  * to switch to the framework's implementation.  See the framework SDK
     43  * documentation for a class overview.
     44  */
     45 public class ListFragment extends Fragment {
     46     static final int INTERNAL_EMPTY_ID = 0x00ff0001;
     47     static final int INTERNAL_PROGRESS_CONTAINER_ID = 0x00ff0002;
     48     static final int INTERNAL_LIST_CONTAINER_ID = 0x00ff0003;
     49 
     50     final private Handler mHandler = new Handler();
     51 
     52     final private Runnable mRequestFocus = new Runnable() {
     53         @Override
     54         public void run() {
     55             mList.focusableViewAvailable(mList);
     56         }
     57     };
     58 
     59     final private AdapterView.OnItemClickListener mOnClickListener
     60             = new AdapterView.OnItemClickListener() {
     61         @Override
     62         public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
     63             onListItemClick((ListView)parent, v, position, id);
     64         }
     65     };
     66 
     67     ListAdapter mAdapter;
     68     ListView mList;
     69     View mEmptyView;
     70     TextView mStandardEmptyView;
     71     View mProgressContainer;
     72     View mListContainer;
     73     CharSequence mEmptyText;
     74     boolean mListShown;
     75 
     76     public ListFragment() {
     77     }
     78 
     79     /**
     80      * Provide default implementation to return a simple list view.  Subclasses
     81      * can override to replace with their own layout.  If doing so, the
     82      * returned view hierarchy <em>must</em> have a ListView whose id
     83      * is {@link android.R.id#list android.R.id.list} and can optionally
     84      * have a sibling view id {@link android.R.id#empty android.R.id.empty}
     85      * that is to be shown when the list is empty.
     86      *
     87      * <p>If you are overriding this method with your own custom content,
     88      * consider including the standard layout {@link android.R.layout#list_content}
     89      * in your layout file, so that you continue to retain all of the standard
     90      * behavior of ListFragment.  In particular, this is currently the only
     91      * way to have the built-in indeterminant progress state be shown.
     92      */
     93     @Override
     94     public View onCreateView(LayoutInflater inflater, ViewGroup container,
     95             Bundle savedInstanceState) {
     96         final Context context = getContext();
     97 
     98         FrameLayout root = new FrameLayout(context);
     99 
    100         // ------------------------------------------------------------------
    101 
    102         LinearLayout pframe = new LinearLayout(context);
    103         pframe.setId(INTERNAL_PROGRESS_CONTAINER_ID);
    104         pframe.setOrientation(LinearLayout.VERTICAL);
    105         pframe.setVisibility(View.GONE);
    106         pframe.setGravity(Gravity.CENTER);
    107 
    108         ProgressBar progress = new ProgressBar(context, null,
    109                 android.R.attr.progressBarStyleLarge);
    110         pframe.addView(progress, new FrameLayout.LayoutParams(
    111                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
    112 
    113         root.addView(pframe, new FrameLayout.LayoutParams(
    114                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    115 
    116         // ------------------------------------------------------------------
    117 
    118         FrameLayout lframe = new FrameLayout(context);
    119         lframe.setId(INTERNAL_LIST_CONTAINER_ID);
    120 
    121         TextView tv = new TextView(context);
    122         tv.setId(INTERNAL_EMPTY_ID);
    123         tv.setGravity(Gravity.CENTER);
    124         lframe.addView(tv, new FrameLayout.LayoutParams(
    125                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    126 
    127         ListView lv = new ListView(context);
    128         lv.setId(android.R.id.list);
    129         lv.setDrawSelectorOnTop(false);
    130         lframe.addView(lv, new FrameLayout.LayoutParams(
    131                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    132 
    133         root.addView(lframe, new FrameLayout.LayoutParams(
    134                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    135 
    136         // ------------------------------------------------------------------
    137 
    138         root.setLayoutParams(new FrameLayout.LayoutParams(
    139                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    140 
    141         return root;
    142     }
    143 
    144     /**
    145      * Attach to list view once the view hierarchy has been created.
    146      */
    147     @Override
    148     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    149         super.onViewCreated(view, savedInstanceState);
    150         ensureList();
    151     }
    152 
    153     /**
    154      * Detach from list view.
    155      */
    156     @Override
    157     public void onDestroyView() {
    158         mHandler.removeCallbacks(mRequestFocus);
    159         mList = null;
    160         mListShown = false;
    161         mEmptyView = mProgressContainer = mListContainer = null;
    162         mStandardEmptyView = null;
    163         super.onDestroyView();
    164     }
    165 
    166     /**
    167      * This method will be called when an item in the list is selected.
    168      * Subclasses should override. Subclasses can call
    169      * getListView().getItemAtPosition(position) if they need to access the
    170      * data associated with the selected item.
    171      *
    172      * @param l The ListView where the click happened
    173      * @param v The view that was clicked within the ListView
    174      * @param position The position of the view in the list
    175      * @param id The row id of the item that was clicked
    176      */
    177     public void onListItemClick(ListView l, View v, int position, long id) {
    178     }
    179 
    180     /**
    181      * Provide the cursor for the list view.
    182      */
    183     public void setListAdapter(ListAdapter adapter) {
    184         boolean hadAdapter = mAdapter != null;
    185         mAdapter = adapter;
    186         if (mList != null) {
    187             mList.setAdapter(adapter);
    188             if (!mListShown && !hadAdapter) {
    189                 // The list was hidden, and previously didn't have an
    190                 // adapter.  It is now time to show it.
    191                 setListShown(true, getView().getWindowToken() != null);
    192             }
    193         }
    194     }
    195 
    196     /**
    197      * Set the currently selected list item to the specified
    198      * position with the adapter's data
    199      *
    200      * @param position
    201      */
    202     public void setSelection(int position) {
    203         ensureList();
    204         mList.setSelection(position);
    205     }
    206 
    207     /**
    208      * Get the position of the currently selected list item.
    209      */
    210     public int getSelectedItemPosition() {
    211         ensureList();
    212         return mList.getSelectedItemPosition();
    213     }
    214 
    215     /**
    216      * Get the cursor row ID of the currently selected list item.
    217      */
    218     public long getSelectedItemId() {
    219         ensureList();
    220         return mList.getSelectedItemId();
    221     }
    222 
    223     /**
    224      * Get the fragment's list view widget.
    225      */
    226     public ListView getListView() {
    227         ensureList();
    228         return mList;
    229     }
    230 
    231     /**
    232      * The default content for a ListFragment has a TextView that can
    233      * be shown when the list is empty.  If you would like to have it
    234      * shown, call this method to supply the text it should use.
    235      */
    236     public void setEmptyText(CharSequence text) {
    237         ensureList();
    238         if (mStandardEmptyView == null) {
    239             throw new IllegalStateException("Can't be used with a custom content view");
    240         }
    241         mStandardEmptyView.setText(text);
    242         if (mEmptyText == null) {
    243             mList.setEmptyView(mStandardEmptyView);
    244         }
    245         mEmptyText = text;
    246     }
    247 
    248     /**
    249      * Control whether the list is being displayed.  You can make it not
    250      * displayed if you are waiting for the initial data to show in it.  During
    251      * this time an indeterminant progress indicator will be shown instead.
    252      *
    253      * <p>Applications do not normally need to use this themselves.  The default
    254      * behavior of ListFragment is to start with the list not being shown, only
    255      * showing it once an adapter is given with {@link #setListAdapter(ListAdapter)}.
    256      * If the list at that point had not been shown, when it does get shown
    257      * it will be do without the user ever seeing the hidden state.
    258      *
    259      * @param shown If true, the list view is shown; if false, the progress
    260      * indicator.  The initial value is true.
    261      */
    262     public void setListShown(boolean shown) {
    263         setListShown(shown, true);
    264     }
    265 
    266     /**
    267      * Like {@link #setListShown(boolean)}, but no animation is used when
    268      * transitioning from the previous state.
    269      */
    270     public void setListShownNoAnimation(boolean shown) {
    271         setListShown(shown, false);
    272     }
    273 
    274     /**
    275      * Control whether the list is being displayed.  You can make it not
    276      * displayed if you are waiting for the initial data to show in it.  During
    277      * this time an indeterminant progress indicator will be shown instead.
    278      *
    279      * @param shown If true, the list view is shown; if false, the progress
    280      * indicator.  The initial value is true.
    281      * @param animate If true, an animation will be used to transition to the
    282      * new state.
    283      */
    284     private void setListShown(boolean shown, boolean animate) {
    285         ensureList();
    286         if (mProgressContainer == null) {
    287             throw new IllegalStateException("Can't be used with a custom content view");
    288         }
    289         if (mListShown == shown) {
    290             return;
    291         }
    292         mListShown = shown;
    293         if (shown) {
    294             if (animate) {
    295                 mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
    296                         getContext(), android.R.anim.fade_out));
    297                 mListContainer.startAnimation(AnimationUtils.loadAnimation(
    298                         getContext(), android.R.anim.fade_in));
    299             } else {
    300                 mProgressContainer.clearAnimation();
    301                 mListContainer.clearAnimation();
    302             }
    303             mProgressContainer.setVisibility(View.GONE);
    304             mListContainer.setVisibility(View.VISIBLE);
    305         } else {
    306             if (animate) {
    307                 mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
    308                         getContext(), android.R.anim.fade_in));
    309                 mListContainer.startAnimation(AnimationUtils.loadAnimation(
    310                         getContext(), android.R.anim.fade_out));
    311             } else {
    312                 mProgressContainer.clearAnimation();
    313                 mListContainer.clearAnimation();
    314             }
    315             mProgressContainer.setVisibility(View.VISIBLE);
    316             mListContainer.setVisibility(View.GONE);
    317         }
    318     }
    319 
    320     /**
    321      * Get the ListAdapter associated with this fragment's ListView.
    322      */
    323     public ListAdapter getListAdapter() {
    324         return mAdapter;
    325     }
    326 
    327     private void ensureList() {
    328         if (mList != null) {
    329             return;
    330         }
    331         View root = getView();
    332         if (root == null) {
    333             throw new IllegalStateException("Content view not yet created");
    334         }
    335         if (root instanceof ListView) {
    336             mList = (ListView)root;
    337         } else {
    338             mStandardEmptyView = (TextView)root.findViewById(INTERNAL_EMPTY_ID);
    339             if (mStandardEmptyView == null) {
    340                 mEmptyView = root.findViewById(android.R.id.empty);
    341             } else {
    342                 mStandardEmptyView.setVisibility(View.GONE);
    343             }
    344             mProgressContainer = root.findViewById(INTERNAL_PROGRESS_CONTAINER_ID);
    345             mListContainer = root.findViewById(INTERNAL_LIST_CONTAINER_ID);
    346             View rawListView = root.findViewById(android.R.id.list);
    347             if (!(rawListView instanceof ListView)) {
    348                 if (rawListView == null) {
    349                     throw new RuntimeException(
    350                             "Your content must have a ListView whose id attribute is " +
    351                             "'android.R.id.list'");
    352                 }
    353                 throw new RuntimeException(
    354                         "Content has view with id attribute 'android.R.id.list' "
    355                         + "that is not a ListView class");
    356             }
    357             mList = (ListView)rawListView;
    358             if (mEmptyView != null) {
    359                 mList.setEmptyView(mEmptyView);
    360             } else if (mEmptyText != null) {
    361                 mStandardEmptyView.setText(mEmptyText);
    362                 mList.setEmptyView(mStandardEmptyView);
    363             }
    364         }
    365         mListShown = true;
    366         mList.setOnItemClickListener(mOnClickListener);
    367         if (mAdapter != null) {
    368             ListAdapter adapter = mAdapter;
    369             mAdapter = null;
    370             setListAdapter(adapter);
    371         } else {
    372             // We are starting without an adapter, so assume we won't
    373             // have our data right away and start with the progress indicator.
    374             if (mProgressContainer != null) {
    375                 setListShown(false, false);
    376             }
    377         }
    378         mHandler.post(mRequestFocus);
    379     }
    380 }
    381