1 /* 2 * Copyright (C) 2017 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.android.setupwizardlib.template; 18 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.graphics.drawable.Drawable; 22 import android.os.Build; 23 import android.os.Build.VERSION_CODES; 24 import android.support.annotation.AttrRes; 25 import android.support.annotation.NonNull; 26 import android.support.annotation.Nullable; 27 import android.util.AttributeSet; 28 import android.view.View; 29 import android.widget.HeaderViewListAdapter; 30 import android.widget.ListAdapter; 31 import android.widget.ListView; 32 33 import com.android.setupwizardlib.R; 34 import com.android.setupwizardlib.TemplateLayout; 35 import com.android.setupwizardlib.items.ItemAdapter; 36 import com.android.setupwizardlib.items.ItemGroup; 37 import com.android.setupwizardlib.items.ItemInflater; 38 import com.android.setupwizardlib.util.DrawableLayoutDirectionHelper; 39 40 /** 41 * A {@link Mixin} for interacting with ListViews. 42 */ 43 public class ListMixin implements Mixin { 44 45 private TemplateLayout mTemplateLayout; 46 47 @Nullable 48 private ListView mListView; 49 50 private Drawable mDivider; 51 private Drawable mDefaultDivider; 52 53 private int mDividerInsetStart; 54 private int mDividerInsetEnd; 55 56 /** 57 * @param layout The layout this mixin belongs to. 58 */ 59 public ListMixin(@NonNull TemplateLayout layout, @Nullable AttributeSet attrs, 60 @AttrRes int defStyleAttr) { 61 mTemplateLayout = layout; 62 63 final Context context = layout.getContext(); 64 final TypedArray a = context.obtainStyledAttributes( 65 attrs, R.styleable.SuwListMixin, defStyleAttr, 0); 66 67 final int entries = a.getResourceId(R.styleable.SuwListMixin_android_entries, 0); 68 if (entries != 0) { 69 final ItemGroup inflated = 70 (ItemGroup) new ItemInflater(context).inflate(entries); 71 setAdapter(new ItemAdapter(inflated)); 72 } 73 int dividerInset = 74 a.getDimensionPixelSize(R.styleable.SuwListMixin_suwDividerInset, -1); 75 if (dividerInset != -1) { 76 setDividerInset(dividerInset); 77 } else { 78 int dividerInsetStart = 79 a.getDimensionPixelSize(R.styleable.SuwListMixin_suwDividerInsetStart, 0); 80 int dividerInsetEnd = 81 a.getDimensionPixelSize(R.styleable.SuwListMixin_suwDividerInsetEnd, 0); 82 setDividerInsets(dividerInsetStart, dividerInsetEnd); 83 } 84 a.recycle(); 85 } 86 87 /** 88 * @return The list view contained in the layout, as marked by {@code @android:id/list}. This 89 * will return {@code null} if the list doesn't exist in the layout. 90 */ 91 public ListView getListView() { 92 return getListViewInternal(); 93 } 94 95 // Client code can assume getListView() will not be null if they know their template contains 96 // the list, but this mixin cannot. Any usages of getListView in this mixin needs null checks. 97 @Nullable 98 private ListView getListViewInternal() { 99 if (mListView == null) { 100 final View list = mTemplateLayout.findManagedViewById(android.R.id.list); 101 if (list instanceof ListView) { 102 mListView = (ListView) list; 103 } 104 } 105 return mListView; 106 } 107 108 /** 109 * List mixin needs to update the dividers if the layout direction has changed. This method 110 * should be called when {@link View#onLayout(boolean, int, int, int, int)} of the template 111 * is called. 112 */ 113 public void onLayout() { 114 if (mDivider == null) { 115 // Update divider in case layout direction has just been resolved 116 updateDivider(); 117 } 118 } 119 120 /** 121 * Gets the adapter of the list view in this layout. If the adapter is a HeaderViewListAdapter, 122 * this method will unwrap it and return the underlying adapter. 123 * 124 * @return The adapter, or {@code null} if there is no list, or if the list has no adapter. 125 */ 126 public ListAdapter getAdapter() { 127 final ListView listView = getListViewInternal(); 128 if (listView != null) { 129 final ListAdapter adapter = listView.getAdapter(); 130 if (adapter instanceof HeaderViewListAdapter) { 131 return ((HeaderViewListAdapter) adapter).getWrappedAdapter(); 132 } 133 return adapter; 134 } 135 return null; 136 } 137 138 /** 139 * Sets the adapter on the list view in this layout. 140 */ 141 public void setAdapter(ListAdapter adapter) { 142 final ListView listView = getListViewInternal(); 143 if (listView != null) { 144 listView.setAdapter(adapter); 145 } 146 } 147 148 /** 149 * @deprecated Use {@link #setDividerInsets(int, int)} instead. 150 */ 151 @Deprecated 152 public void setDividerInset(int inset) { 153 setDividerInsets(inset, 0); 154 } 155 156 /** 157 * Sets the start inset of the divider. This will use the default divider drawable set in the 158 * theme and apply insets to it. 159 * 160 * @param start The number of pixels to inset on the "start" side of the list divider. Typically 161 * this will be either {@code @dimen/suw_items_glif_icon_divider_inset} or 162 * {@code @dimen/suw_items_glif_text_divider_inset}. 163 * @param end The number of pixels to inset on the "end" side of the list divider. 164 */ 165 public void setDividerInsets(int start, int end) { 166 mDividerInsetStart = start; 167 mDividerInsetEnd = end; 168 updateDivider(); 169 } 170 171 /** 172 * @return The number of pixels inset on the start side of the divider. 173 * @deprecated This is the same as {@link #getDividerInsetStart()}. Use that instead. 174 */ 175 @Deprecated 176 public int getDividerInset() { 177 return getDividerInsetStart(); 178 } 179 180 /** 181 * @return The number of pixels inset on the start side of the divider. 182 */ 183 public int getDividerInsetStart() { 184 return mDividerInsetStart; 185 } 186 187 /** 188 * @return The number of pixels inset on the end side of the divider. 189 */ 190 public int getDividerInsetEnd() { 191 return mDividerInsetEnd; 192 } 193 194 private void updateDivider() { 195 final ListView listView = getListViewInternal(); 196 if (listView == null) { 197 return; 198 } 199 boolean shouldUpdate = true; 200 if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) { 201 shouldUpdate = mTemplateLayout.isLayoutDirectionResolved(); 202 } 203 if (shouldUpdate) { 204 if (mDefaultDivider == null) { 205 mDefaultDivider = listView.getDivider(); 206 } 207 mDivider = DrawableLayoutDirectionHelper.createRelativeInsetDrawable( 208 mDefaultDivider, 209 mDividerInsetStart /* start */, 210 0 /* top */, 211 mDividerInsetEnd /* end */, 212 0 /* bottom */, 213 mTemplateLayout); 214 listView.setDivider(mDivider); 215 } 216 } 217 218 /** 219 * @return The drawable used as the divider. 220 */ 221 public Drawable getDivider() { 222 return mDivider; 223 } 224 } 225