1 package org.skia.viewer; 2 3 import android.text.Editable; 4 import android.text.TextWatcher; 5 import android.view.LayoutInflater; 6 import android.view.SurfaceView; 7 import android.view.View; 8 import android.view.ViewGroup; 9 import android.widget.AdapterView; 10 import android.widget.BaseAdapter; 11 import android.widget.CompoundButton; 12 import android.widget.EditText; 13 import android.widget.LinearLayout; 14 import android.widget.Spinner; 15 import android.widget.Switch; 16 import android.widget.TextView; 17 18 import org.json.JSONArray; 19 import org.json.JSONException; 20 import org.json.JSONObject; 21 22 import java.util.ArrayList; 23 24 /* 25 The navigation drawer requires ListView, so we implemented this BaseAdapter for that ListView. 26 However, the ListView does not provide good support for updating just a single child view. 27 For example, a frequently changed child view such as FPS state will reset the spinner of 28 all other child views; although I didn't change other child views and directly return 29 the convertView in BaseAdapter.getView(int position, View convertView, ViewGroup parent). 30 31 Therefore, our adapter only returns one LinearLayout for the ListView. 32 Within that LinearLayout, we maintain views ourselves so we can efficiently update its children. 33 */ 34 public class StateAdapter extends BaseAdapter implements AdapterView.OnItemSelectedListener { 35 private static final String NAME = "name"; 36 private static final String VALUE = "value"; 37 private static final String OPTIONS = "options"; 38 private static final String BACKEND_STATE_NAME = "Backend"; 39 private static final String FPS_STATE_NAME = "FPS"; 40 private static final String REFRESH_STATE_NAME = "Refresh"; 41 private static final String ON = "ON"; 42 private static final String OFF = "OFF"; 43 private static final int FILTER_LENGTH = 20; 44 45 private ViewerActivity mViewerActivity; 46 private LinearLayout mLayout; 47 private JSONArray mStateJson; 48 private TextView mFPSFloatText; 49 50 public StateAdapter(ViewerActivity viewerActivity) { 51 mViewerActivity = viewerActivity; 52 mFPSFloatText = (TextView) viewerActivity.findViewById(R.id.fpsFloatText); 53 try { 54 mStateJson = new JSONArray("[{\"name\": \"Please\", " + 55 "\"value\": \"Initialize\", \"options\": []}]"); 56 } catch (JSONException e) { 57 e.printStackTrace(); 58 } 59 } 60 61 public void setState(String stateJson) { 62 try { 63 mStateJson = new JSONArray(stateJson); 64 if (mLayout != null) { 65 updateDrawer(); 66 } else { 67 notifyDataSetChanged(); 68 } 69 } catch (JSONException e) { 70 e.printStackTrace(); 71 } 72 } 73 74 // The first list item is the mLayout that contains a list of state items 75 // The second list item is the toggle for float FPS 76 @Override 77 public int getCount() { 78 return 2; 79 } 80 81 @Override 82 public Object getItem(int position) { 83 return null; 84 } 85 86 @Override 87 public long getItemId(int position) { 88 return 0; 89 } 90 91 @Override 92 public View getView(int position, View convertView, ViewGroup parent) { 93 switch (position) { 94 case 0: { 95 if (mLayout == null) { 96 mLayout = new LinearLayout(mViewerActivity); 97 mLayout.setOrientation(LinearLayout.VERTICAL); 98 updateDrawer(); 99 } 100 return mLayout; 101 } 102 case 1: { 103 View view = LayoutInflater.from(mViewerActivity).inflate(R.layout.fps_toggle, null); 104 Switch theSwitch = (Switch) view.findViewById(R.id.theSwitch); 105 theSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){ 106 @Override 107 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 108 mFPSFloatText.setVisibility(isChecked ? View.VISIBLE : View.INVISIBLE); 109 // Quickly set the bool fRefresh in native Viewer app for continuous refresh 110 mViewerActivity.onStateChanged(REFRESH_STATE_NAME, isChecked ? ON : OFF); 111 } 112 }); 113 return view; 114 } 115 default: { 116 return null; 117 } 118 } 119 } 120 121 private void populateView(JSONObject item, View view) throws JSONException { 122 LinearLayout itemView = (LinearLayout) view; 123 TextView nameText = (TextView) itemView.findViewById(R.id.nameText); 124 TextView valueText = (TextView) itemView.findViewById(R.id.valueText); 125 Spinner optionSpinner = (Spinner) itemView.findViewById(R.id.optionSpinner); 126 127 String value = item.getString(VALUE); 128 itemView.setTag(item.toString()); // To save unnecessary view update 129 itemView.setTag(R.integer.value_tag_key, value); 130 131 nameText.setText(item.getString(NAME)); 132 133 if (nameText.getText().equals(FPS_STATE_NAME) && mFPSFloatText != null) { 134 mFPSFloatText.setText(value); 135 // Don't show FPS in the drawer. We'll show it in the float text. 136 itemView.setVisibility(View.GONE); 137 } 138 139 JSONArray options = item.getJSONArray(OPTIONS); 140 if (options.length() == 0) { 141 valueText.setText(value); 142 valueText.setVisibility(View.VISIBLE); 143 optionSpinner.setVisibility(View.GONE); 144 } else { 145 ArrayList<String> optionList = new ArrayList<>(); 146 String[] optionStrings = new String[options.length()]; 147 for (int j = 0; j < options.length(); j++) { 148 optionList.add(options.getString(j)); 149 } 150 final OptionAdapter adapter = new OptionAdapter(mViewerActivity, 151 android.R.layout.simple_spinner_dropdown_item, optionList, optionSpinner); 152 adapter.setCurrentOption(value); 153 optionSpinner.setAdapter(adapter); 154 if (optionStrings.length >= FILTER_LENGTH) { 155 View existingView = itemView.getChildAt(1); 156 if (!(existingView instanceof EditText)) { 157 EditText filterText = new EditText(mViewerActivity); 158 filterText.setHint("Filter"); 159 itemView.addView(filterText, 1); 160 filterText.addTextChangedListener(new TextWatcher() { 161 @Override 162 public void beforeTextChanged(CharSequence s, int start, int cnt, 163 int after) { 164 } 165 166 @Override 167 public void onTextChanged(CharSequence s, int start, int before, int cnt) { 168 } 169 170 @Override 171 public void afterTextChanged(Editable s) { 172 adapter.getFilter().filter(s.toString()); 173 } 174 }); 175 } 176 } 177 optionSpinner.setSelection(optionList.indexOf(value)); 178 optionSpinner.setOnItemSelectedListener(this); 179 optionSpinner.setVisibility(View.VISIBLE); 180 valueText.setVisibility(View.GONE); 181 } 182 } 183 private View inflateItemView(JSONObject item) throws JSONException { 184 LinearLayout itemView = (LinearLayout) 185 LayoutInflater.from(mViewerActivity).inflate(R.layout.state_item, null); 186 populateView(item, itemView); 187 return itemView; 188 } 189 190 private void updateDrawer() { 191 try { 192 if (mStateJson.length() < mLayout.getChildCount()) { 193 mLayout.removeViews( 194 mStateJson.length(), mLayout.getChildCount() - mStateJson.length()); 195 } 196 for (int i = 0; i < mStateJson.length(); i++) { 197 JSONObject stateObject = mStateJson.getJSONObject(i); 198 View childView = mLayout.getChildAt(i); 199 if (childView != null) { 200 if (stateObject.toString().equals(childView.getTag())) { 201 continue; // No update, reuse the old view and skip the remaining step 202 } else { 203 populateView(stateObject, childView); 204 } 205 } else { 206 mLayout.addView(inflateItemView(stateObject), i); 207 } 208 } 209 } catch (JSONException e) { 210 e.printStackTrace(); 211 } 212 } 213 214 @Override 215 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 216 if (view == null) { 217 return; 218 } 219 View stateItem = (View) parent.getParent(); 220 String stateName = ((TextView) stateItem.findViewById(R.id.nameText)).getText().toString(); 221 String stateValue = ((TextView) view).getText().toString(); 222 if (!stateValue.equals(stateItem.getTag(R.integer.value_tag_key))) { 223 stateItem.setTag(null); // Reset the tag to let updateDrawer update this item view. 224 mViewerActivity.onStateChanged(stateName, stateValue); 225 } 226 227 // Due to the current Android limitation, we're required to recreate the SurfaceView for 228 // switching to/from the Raster backend. 229 // (Although we can switch between GPU backend without recreating the SurfaceView.) 230 final Object oldValue = stateItem.getTag(R.integer.value_tag_key); 231 if (stateName.equals(BACKEND_STATE_NAME) 232 && oldValue != null && !stateValue.equals(oldValue)) { 233 LinearLayout mainLayout = (LinearLayout) mViewerActivity.findViewById(R.id.mainLayout); 234 mainLayout.removeAllViews(); 235 SurfaceView surfaceView = new SurfaceView(mViewerActivity); 236 surfaceView.setId(R.id.surfaceView); 237 surfaceView.getHolder().addCallback(mViewerActivity); 238 surfaceView.setOnTouchListener(mViewerActivity); 239 mainLayout.addView(surfaceView); 240 } 241 } 242 243 @Override 244 public void onNothingSelected(AdapterView<?> parent) { 245 // do nothing 246 } 247 } 248