1 /* 2 * Copyright 2014 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 com.example.android.adaptertransition; 18 19 import android.os.Bundle; 20 import android.support.v4.app.ActivityCompat; 21 import android.support.v4.app.Fragment; 22 import android.transition.AutoTransition; 23 import android.transition.Scene; 24 import android.transition.Transition; 25 import android.transition.TransitionManager; 26 import android.view.LayoutInflater; 27 import android.view.Menu; 28 import android.view.MenuInflater; 29 import android.view.MenuItem; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.widget.AbsListView; 33 import android.widget.FrameLayout; 34 import android.widget.GridView; 35 import android.widget.ListView; 36 import android.widget.Toast; 37 38 /** 39 * Main screen for AdapterTransition sample. 40 */ 41 public class AdapterTransitionFragment extends Fragment implements Transition.TransitionListener { 42 43 /** 44 * Since the transition framework requires all relevant views in a view hierarchy to be marked 45 * with IDs, we use this ID to mark the root view. 46 */ 47 private static final int ROOT_ID = 1; 48 49 /** 50 * A tag for saving state whether the mAbsListView is ListView or GridView. 51 */ 52 private static final String STATE_IS_LISTVIEW = "is_listview"; 53 54 /** 55 * This is where we place our AdapterView (ListView / GridView). 56 */ 57 private FrameLayout mContent; 58 59 /** 60 * This is where we carry out the transition. 61 */ 62 private FrameLayout mCover; 63 64 /** 65 * This list shows our contents. It can be ListView or GridView, and we toggle between them 66 * using the transition framework. 67 */ 68 private AbsListView mAbsListView; 69 70 /** 71 * This is our contents. 72 */ 73 private MeatAdapter mAdapter; 74 75 public static AdapterTransitionFragment newInstance() { 76 return new AdapterTransitionFragment(); 77 } 78 79 public AdapterTransitionFragment() { 80 } 81 82 @Override 83 public void onCreate(Bundle savedInstanceState) { 84 super.onCreate(savedInstanceState); 85 setHasOptionsMenu(true); 86 } 87 88 @Override 89 public View onCreateView(LayoutInflater inflater, ViewGroup container, 90 Bundle savedInstanceState) { 91 // If savedInstanceState is available, we restore the state whether the list is a ListView 92 // or a GridView. 93 boolean isListView; 94 if (null == savedInstanceState) { 95 isListView = true; 96 } else { 97 isListView = savedInstanceState.getBoolean(STATE_IS_LISTVIEW, true); 98 } 99 inflateAbsList(inflater, container, isListView); 100 return inflater.inflate(R.layout.fragment_adapter_transition, container, false); 101 } 102 103 @Override 104 public void onSaveInstanceState(Bundle outState) { 105 super.onSaveInstanceState(outState); 106 outState.putBoolean(STATE_IS_LISTVIEW, mAbsListView instanceof ListView); 107 } 108 109 @Override 110 public void onViewCreated(View view, Bundle savedInstanceState) { 111 // Retaining references for FrameLayouts that we use later. 112 mContent = (FrameLayout) view.findViewById(R.id.content); 113 mCover = (FrameLayout) view.findViewById(R.id.cover); 114 // We are attaching the list to the screen here. 115 mContent.addView(mAbsListView); 116 } 117 118 @Override 119 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 120 inflater.inflate(R.menu.fragment_adapter_transition, menu); 121 } 122 123 @Override 124 public void onPrepareOptionsMenu(Menu menu) { 125 // We change the look of the icon every time the user toggles between list and grid. 126 MenuItem item = menu.findItem(R.id.action_toggle); 127 if (null != item) { 128 if (mAbsListView instanceof ListView) { 129 item.setIcon(R.drawable.ic_action_grid); 130 item.setTitle(R.string.show_as_grid); 131 } else { 132 item.setIcon(R.drawable.ic_action_list); 133 item.setTitle(R.string.show_as_list); 134 } 135 } 136 } 137 138 @Override 139 public boolean onOptionsItemSelected(MenuItem item) { 140 switch (item.getItemId()) { 141 case R.id.action_toggle: { 142 toggle(); 143 return true; 144 } 145 } 146 return false; 147 } 148 149 @Override 150 public void onTransitionStart(Transition transition) { 151 } 152 154 @Override 155 public void onTransitionEnd(Transition transition) { 156 // When the transition ends, we remove all the views from the overlay and hide it. 157 mCover.removeAllViews(); 158 mCover.setVisibility(View.INVISIBLE); 159 } 161 162 @Override 163 public void onTransitionCancel(Transition transition) { 164 } 165 166 @Override 167 public void onTransitionPause(Transition transition) { 168 } 169 170 @Override 171 public void onTransitionResume(Transition transition) { 172 } 173 174 /** 175 * Inflate a ListView or a GridView with a corresponding ListAdapter. 176 * 177 * @param inflater The LayoutInflater. 178 * @param container The ViewGroup that contains this AbsListView. The AbsListView won't be 179 * attached to it. 180 * @param inflateListView Pass true to inflate a ListView, or false to inflate a GridView. 181 */ 182 private void inflateAbsList(LayoutInflater inflater, ViewGroup container, 183 boolean inflateListView) { 184 if (inflateListView) { 185 mAbsListView = (AbsListView) inflater.inflate(R.layout.fragment_meat_list, 186 container, false); 187 mAdapter = new MeatAdapter(inflater, R.layout.item_meat_list); 188 } else { 189 mAbsListView = (AbsListView) inflater.inflate(R.layout.fragment_meat_grid, 190 container, false); 191 mAdapter = new MeatAdapter(inflater, R.layout.item_meat_grid); 192 } 193 mAbsListView.setAdapter(mAdapter); 194 mAbsListView.setOnItemClickListener(mAdapter); 195 } 196 197 /** 198 * Toggle the UI between ListView and GridView. 199 */ 200 private void toggle() { 201 // We use mCover as the overlay on which we carry out the transition. 202 mCover.setVisibility(View.VISIBLE); 203 // This FrameLayout holds all the visible views in the current list or grid. We use this as 204 // the starting Scene of the Transition later. 205 FrameLayout before = copyVisibleViews(); 206 FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( 207 FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); 208 mCover.addView(before, params); 209 // Swap the actual list. 210 swapAbsListView(); 211 // We also swap the icon for the toggle button. 212 ActivityCompat.invalidateOptionsMenu(getActivity()); 213 // It is now ready to start the transition. 214 mAbsListView.post(new Runnable() { 215 @Override 216 public void run() { 218 Scene scene = new Scene(mCover, copyVisibleViews()); 219 Transition transition = new AutoTransition(); 220 transition.addListener(AdapterTransitionFragment.this); 221 TransitionManager.go(scene, transition); 223 } 224 }); 225 } 226 227 /** 228 * Swap ListView with GridView, or GridView with ListView. 229 */ 230 private void swapAbsListView() { 231 // We save the current scrolling position before removing the current list. 232 int first = mAbsListView.getFirstVisiblePosition(); 233 // If the current list is a GridView, we replace it with a ListView. If it is a ListView, 234 // a GridView. 235 LayoutInflater inflater = LayoutInflater.from(getActivity()); 236 inflateAbsList(inflater, (ViewGroup) mAbsListView.getParent(), 237 mAbsListView instanceof GridView); 238 mAbsListView.setAdapter(mAdapter); 239 // We restore the scrolling position here. 240 mAbsListView.setSelection(first); 241 // The new list is ready, and we replace the existing one with it. 242 mContent.removeAllViews(); 243 mContent.addView(mAbsListView); 244 } 245 246 /** 247 * Copy all the visible views in the mAbsListView into a new FrameLayout and return it. 248 * 249 * @return a FrameLayout with all the visible views inside. 250 */ 251 private FrameLayout copyVisibleViews() { 252 // This is the FrameLayout we return afterwards. 253 FrameLayout layout = new FrameLayout(getActivity()); 254 // The transition framework requires to set ID for all views to be animated. 255 layout.setId(ROOT_ID); 256 // We only copy visible views. 257 int first = mAbsListView.getFirstVisiblePosition(); 258 int index = 0; 259 while (true) { 260 // This is one of the views that we copy. Note that the argument for getChildAt is a 261 // zero-oriented index, and it doesn't usually match with its position in the list. 262 View source = mAbsListView.getChildAt(index); 263 if (null == source) { 264 break; 265 } 266 // This is the copy of the original view. 267 View destination = mAdapter.getView(first + index, null, layout); 268 assert destination != null; 269 destination.setId(ROOT_ID + first + index); 270 FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( 271 source.getWidth(), source.getHeight()); 272 params.leftMargin = (int) source.getX(); 273 params.topMargin = (int) source.getY(); 274 layout.addView(destination, params); 275 ++index; 276 } 277 return layout; 278 } 279 280 }