1 /* 2 * Copyright (C) 2023 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 android.widget; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.os.Bundle; 22 import android.view.View; 23 import android.view.ViewGroup; 24 import android.view.Window; 25 import android.widget.Button; 26 import android.widget.InternalSelectionView; 27 import android.widget.LinearLayout; 28 import android.widget.ScrollView; 29 import android.widget.TextView; 30 31 import com.google.android.collect.Lists; 32 33 import java.util.List; 34 35 /** 36 * Utility base class for creating scroll view scenarios, allowing you to add 37 * a series of different kinds of views arranged vertically, taking up a 38 * specified amount of the screen height. 39 */ 40 public abstract class ScrollViewScenario extends Activity { 41 42 /** 43 * Holds content of scroll view 44 */ 45 private LinearLayout mLinearLayout; 46 47 /** 48 * The actual scroll view 49 */ 50 private ScrollView mScrollView; 51 52 53 /** 54 * What we need of each view that the user wants: the view, and the ratio 55 * to the screen height for its desired height. 56 */ 57 private interface ViewFactory { create(final Context context)58 View create(final Context context); 59 getHeightRatio()60 float getHeightRatio(); 61 } 62 63 /** 64 * Partially implement ViewFactory given a height ratio. 65 * A negative height ratio means that WRAP_CONTENT will be used as height 66 */ 67 private static abstract class ViewFactoryBase implements ViewFactory { 68 69 private float mHeightRatio; 70 71 @SuppressWarnings({"UnusedDeclaration"}) ViewFactoryBase()72 private ViewFactoryBase() {throw new UnsupportedOperationException("don't call this!");} 73 ViewFactoryBase(float heightRatio)74 protected ViewFactoryBase(float heightRatio) { 75 mHeightRatio = heightRatio; 76 } 77 getHeightRatio()78 public float getHeightRatio() { 79 return mHeightRatio; 80 } 81 } 82 83 /** 84 * Builder for selecting the views to be vertically arranged in the scroll 85 * view. 86 */ 87 @SuppressWarnings({"JavaDoc"}) 88 public static class Params { 89 90 List<ViewFactory> mViewFactories = Lists.newArrayList(); 91 92 int mTopPadding = 0; 93 int mBottomPadding = 0; 94 95 /** 96 * Add a text view. 97 * @param text The text of the text view. 98 * @param heightRatio The view's height will be this * the screen height. 99 */ addTextView(final String text, float heightRatio)100 public Params addTextView(final String text, float heightRatio) { 101 mViewFactories.add(new ViewFactoryBase(heightRatio) { 102 public View create(final Context context) { 103 final TextView tv = new TextView(context); 104 tv.setText(text); 105 return tv; 106 } 107 }); 108 return this; 109 } 110 111 /** 112 * Add multiple text views. 113 * @param numViews the number of views to add. 114 * @param textPrefix The text to prepend to each text view. 115 * @param heightRatio The view's height will be this * the screen height. 116 */ addTextViews(int numViews, String textPrefix, float heightRatio)117 public Params addTextViews(int numViews, String textPrefix, float heightRatio) { 118 for (int i = 0; i < numViews; i++) { 119 addTextView(textPrefix + i, heightRatio); 120 } 121 return this; 122 } 123 124 /** 125 * Add a button. 126 * @param text The text of the button. 127 * @param heightRatio The view's height will be this * the screen height. 128 */ addButton(final String text, float heightRatio)129 public Params addButton(final String text, float heightRatio) { 130 mViewFactories.add(new ViewFactoryBase(heightRatio) { 131 public View create(final Context context) { 132 final Button button = new Button(context); 133 button.setText(text); 134 return button; 135 } 136 }); 137 return this; 138 } 139 140 /** 141 * Add multiple buttons. 142 * @param numButtons the number of views to add. 143 * @param textPrefix The text to prepend to each button. 144 * @param heightRatio The view's height will be this * the screen height. 145 */ addButtons(int numButtons, String textPrefix, float heightRatio)146 public Params addButtons(int numButtons, String textPrefix, float heightRatio) { 147 for (int i = 0; i < numButtons; i++) { 148 addButton(textPrefix + i, heightRatio); 149 } 150 return this; 151 } 152 153 /** 154 * Add an {@link InternalSelectionView}. 155 * @param numRows The number of rows in the internal selection view. 156 * @param heightRatio The view's height will be this * the screen height. 157 */ addInternalSelectionView(final int numRows, float heightRatio)158 public Params addInternalSelectionView(final int numRows, float heightRatio) { 159 mViewFactories.add(new ViewFactoryBase(heightRatio) { 160 public View create(final Context context) { 161 return new InternalSelectionView(context, numRows, "isv"); 162 } 163 }); 164 return this; 165 } 166 167 /** 168 * Add a sublayout of buttons as a single child of the scroll view. 169 * @param numButtons The number of buttons in the sub layout 170 * @param heightRatio The layout's height will be this * the screen height. 171 */ addVerticalLLOfButtons(final String prefix, final int numButtons, float heightRatio)172 public Params addVerticalLLOfButtons(final String prefix, final int numButtons, float heightRatio) { 173 mViewFactories.add(new ViewFactoryBase(heightRatio) { 174 175 public View create(Context context) { 176 final LinearLayout ll = new LinearLayout(context); 177 ll.setOrientation(LinearLayout.VERTICAL); 178 179 // fill width, equally weighted on height 180 final LinearLayout.LayoutParams lp = 181 new LinearLayout.LayoutParams( 182 ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f); 183 for (int i = 0; i < numButtons; i++) { 184 final Button button = new Button(context); 185 button.setText(prefix + i); 186 ll.addView(button, lp); 187 } 188 189 return ll; 190 } 191 }); 192 return this; 193 } 194 addPaddingToScrollView(int topPadding, int bottomPadding)195 public Params addPaddingToScrollView(int topPadding, int bottomPadding) { 196 mTopPadding = topPadding; 197 mBottomPadding = bottomPadding; 198 199 return this; 200 } 201 } 202 203 /** 204 * Override this and initialized the views in the scroll view. 205 * @param params Used to configure the contents of the scroll view. 206 */ init(Params params)207 protected abstract void init(Params params); 208 getLinearLayout()209 public LinearLayout getLinearLayout() { 210 return mLinearLayout; 211 } 212 getScrollView()213 public ScrollView getScrollView() { 214 return mScrollView; 215 } 216 217 /** 218 * Get the child contained within the vertical linear layout of the 219 * scroll view. 220 * @param index The index within the linear layout. 221 * @return the child within the vertical linear layout of the scroll view 222 * at the specified index. 223 */ 224 @SuppressWarnings({"unchecked"}) getContentChildAt(int index)225 public <T extends View> T getContentChildAt(int index) { 226 return (T) mLinearLayout.getChildAt(index); 227 } 228 229 /** 230 * Hook for changing how scroll view's are created. 231 */ 232 @SuppressWarnings({"JavaDoc"}) createScrollView()233 protected ScrollView createScrollView() { 234 return new ScrollView(this); 235 } 236 237 @Override onCreate(Bundle savedInstanceState)238 protected void onCreate(Bundle savedInstanceState) { 239 super.onCreate(savedInstanceState); 240 241 // for test stability, turn off title bar 242 requestWindowFeature(Window.FEATURE_NO_TITLE); 243 int screenHeight = getWindowManager().getCurrentWindowMetrics().getBounds().height() 244 - 25; 245 mLinearLayout = new LinearLayout(this); 246 mLinearLayout.setOrientation(LinearLayout.VERTICAL); 247 248 // initialize params 249 final Params params = new Params(); 250 init(params); 251 252 // create views specified by params 253 for (ViewFactory viewFactory : params.mViewFactories) { 254 int height = ViewGroup.LayoutParams.WRAP_CONTENT; 255 if (viewFactory.getHeightRatio() >= 0) { 256 height = (int) (viewFactory.getHeightRatio() * screenHeight); 257 } 258 final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( 259 ViewGroup.LayoutParams.MATCH_PARENT, height); 260 mLinearLayout.addView(viewFactory.create(this), lp); 261 } 262 263 mScrollView = createScrollView(); 264 mScrollView.setPadding(0, params.mTopPadding, 0, params.mBottomPadding); 265 mScrollView.addView(mLinearLayout, new ViewGroup.LayoutParams( 266 ViewGroup.LayoutParams.MATCH_PARENT, 267 ViewGroup.LayoutParams.MATCH_PARENT)); 268 269 // no animation to speed up tests 270 mScrollView.setSmoothScrollingEnabled(false); 271 272 setContentView(mScrollView); 273 mScrollView.post(() -> mScrollView.restoreDefaultFocus()); 274 } 275 } 276