1 /* 2 * Copyright (C) 2015 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.packageinstaller.permission.ui.wear; 18 19 import android.app.Fragment; 20 import android.content.Context; 21 import android.os.Bundle; 22 import android.support.v7.widget.RecyclerView; 23 import android.support.wearable.view.WearableListView; 24 import android.text.Editable; 25 import android.text.TextWatcher; 26 import android.util.TypedValue; 27 import android.view.Gravity; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.view.WindowInsets; 31 import android.widget.LinearLayout; 32 import android.widget.TextView; 33 34 import com.android.packageinstaller.permission.ui.wear.settings.ViewUtils; 35 import com.android.packageinstaller.R; 36 37 /** 38 * Base settings Fragment that shows a title at the top of the page. 39 */ 40 public abstract class TitledSettingsFragment extends Fragment implements 41 View.OnLayoutChangeListener, WearableListView.ClickListener { 42 43 private static final int ITEM_CHANGE_DURATION_MS = 120; 44 45 private static final String TAG = "TitledSettingsFragment"; 46 private int mInitialHeaderHeight; 47 48 protected TextView mHeader; 49 protected TextView mDetails; 50 protected WearableListView mWheel; 51 52 private int mCharLimitShortTitle; 53 private int mCharLimitLine; 54 private int mChinOffset; 55 56 private TextWatcher mHeaderTextWatcher = new TextWatcher() { 57 @Override 58 public void beforeTextChanged(CharSequence s, int start, int count, int after) {} 59 60 @Override 61 public void onTextChanged(CharSequence s, int start, int before, int count) {} 62 63 @Override 64 public void afterTextChanged(Editable editable) { 65 adjustHeaderSize(); 66 } 67 }; 68 adjustHeaderTranslation()69 private void adjustHeaderTranslation() { 70 int translation = 0; 71 if (mWheel.getChildCount() > 0) { 72 translation = mWheel.getCentralViewTop() - mWheel.getChildAt(0).getTop(); 73 } 74 75 float newTranslation = Math.min(Math.max(-mInitialHeaderHeight, -translation), 0); 76 77 int position = mWheel.getChildAdapterPosition(mWheel.getChildAt(0)); 78 if (position == 0 || newTranslation < 0) { 79 mHeader.setTranslationY(newTranslation); 80 } 81 } 82 83 @Override onTopEmptyRegionClick()84 public void onTopEmptyRegionClick() { 85 } 86 87 @Override onCreate(Bundle savedInstanceState)88 public void onCreate(Bundle savedInstanceState) { 89 super.onCreate(savedInstanceState); 90 mCharLimitShortTitle = getResources().getInteger(R.integer.short_title_length); 91 mCharLimitLine = getResources().getInteger(R.integer.char_limit_per_line); 92 } 93 94 @Override onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)95 public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, 96 int oldTop, int oldRight, int oldBottom) { 97 if (view == mHeader) { 98 mInitialHeaderHeight = bottom - top; 99 if (ViewUtils.getIsCircular(getContext())) { 100 // We are adding more margin on circular screens, so we need to account for it and use 101 // it for hiding the header. 102 mInitialHeaderHeight += 103 ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).topMargin; 104 } 105 } else if (view == mWheel) { 106 adjustHeaderTranslation(); 107 } 108 } 109 initializeLayout(RecyclerView.Adapter adapter)110 protected void initializeLayout(RecyclerView.Adapter adapter) { 111 View v = getView(); 112 mWheel = (WearableListView) v.findViewById(R.id.wheel); 113 114 mHeader = (TextView) v.findViewById(R.id.header); 115 mHeader.addOnLayoutChangeListener(this); 116 mHeader.addTextChangedListener(mHeaderTextWatcher); 117 118 mDetails = (TextView) v.findViewById(R.id.details); 119 mDetails.addOnLayoutChangeListener(this); 120 121 mWheel.setAdapter(adapter); 122 mWheel.addOnScrollListener(new RecyclerView.OnScrollListener() { 123 @Override 124 public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 125 } 126 127 @Override 128 public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 129 adjustHeaderTranslation(); 130 } 131 }); 132 mWheel.setClickListener(this); 133 mWheel.addOnLayoutChangeListener(this); 134 135 // Decrease item change animation duration to approximately half of the default duration. 136 RecyclerView.ItemAnimator itemAnimator = mWheel.getItemAnimator(); 137 itemAnimator.setChangeDuration(ITEM_CHANGE_DURATION_MS); 138 139 adjustHeaderSize(); 140 141 positionOnCircular(getContext(), mHeader, mDetails, mWheel); 142 } 143 positionOnCircular(Context context, View header, View details, final ViewGroup wheel)144 public void positionOnCircular(Context context, View header, View details, 145 final ViewGroup wheel) { 146 if (ViewUtils.getIsCircular(context)) { 147 LinearLayout.LayoutParams params = 148 (LinearLayout.LayoutParams) header.getLayoutParams(); 149 params.topMargin = (int) context.getResources().getDimension( 150 R.dimen.settings_header_top_margin_circular); 151 // Note that the margins are made symmetrical here. Since they're symmetrical we choose 152 // the smaller value to maximize usable width. 153 final int margin = (int) Math.min(context.getResources().getDimension( 154 R.dimen.round_content_padding_left), context.getResources().getDimension( 155 R.dimen.round_content_padding_right)); 156 params.leftMargin = margin; 157 params.rightMargin = margin; 158 params.gravity = Gravity.CENTER_HORIZONTAL; 159 header.setLayoutParams(params); 160 details.setLayoutParams(params); 161 162 if (header instanceof TextView) { 163 ((TextView) header).setGravity(Gravity.CENTER); 164 } 165 if (details instanceof TextView) { 166 ((TextView) details).setGravity(Gravity.CENTER); 167 } 168 169 final int leftPadding = (int) context.getResources().getDimension( 170 R.dimen.round_content_padding_left); 171 final int rightPadding = (int) context.getResources().getDimension( 172 R.dimen.round_content_padding_right); 173 final int topPadding = (int) context.getResources().getDimension( 174 R.dimen.settings_wearable_list_view_vertical_padding_round); 175 final int bottomPadding = (int) context.getResources().getDimension( 176 R.dimen.settings_wearable_list_view_vertical_padding_round); 177 wheel.setPadding(leftPadding, topPadding, rightPadding, mChinOffset + bottomPadding); 178 wheel.setClipToPadding(false); 179 180 wheel.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { 181 @Override 182 public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { 183 mChinOffset = insets.getSystemWindowInsetBottom(); 184 wheel.setPadding(leftPadding, topPadding, rightPadding, 185 mChinOffset + bottomPadding); 186 // This listener is invoked after each time we navigate to SettingsActivity and 187 // it keeps adding padding. We need to disable it after the first update. 188 v.setOnApplyWindowInsetsListener(null); 189 return insets.consumeSystemWindowInsets(); 190 } 191 }); 192 } else { 193 int leftPadding = (int) context.getResources().getDimension( 194 R.dimen.content_padding_left); 195 wheel.setPadding(leftPadding, wheel.getPaddingTop(), wheel.getPaddingRight(), 196 wheel.getPaddingBottom()); 197 } 198 } 199 adjustHeaderSize()200 private void adjustHeaderSize() { 201 int length = mHeader.length(); 202 203 if (length <= mCharLimitShortTitle) { 204 mHeader.setTextSize(TypedValue.COMPLEX_UNIT_PX, 205 getResources().getDimensionPixelSize( 206 R.dimen.setting_short_header_text_size)); 207 } else { 208 mHeader.setTextSize(TypedValue.COMPLEX_UNIT_PX, 209 getResources().getDimensionPixelSize( 210 R.dimen.setting_long_header_text_size)); 211 } 212 213 boolean singleLine = length <= mCharLimitLine; 214 215 float height = getResources().getDimension(R.dimen.settings_header_base_height); 216 if (!singleLine) { 217 height += getResources().getDimension(R.dimen.setting_header_extra_line_height); 218 } 219 mHeader.setMinHeight((int) height); 220 221 LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mHeader.getLayoutParams(); 222 final Context context = getContext(); 223 if (!singleLine) { 224 // Make the top margin a little bit smaller so there is more space for the title. 225 if (ViewUtils.getIsCircular(context)) { 226 params.topMargin = getResources().getDimensionPixelSize( 227 R.dimen.settings_header_top_margin_circular_multiline); 228 } else { 229 params.topMargin = getResources().getDimensionPixelSize( 230 R.dimen.settings_header_top_margin_multiline); 231 } 232 } else { 233 if (ViewUtils.getIsCircular(context)) { 234 params.topMargin = getResources().getDimensionPixelSize( 235 R.dimen.settings_header_top_margin_circular); 236 } else { 237 params.topMargin = getResources().getDimensionPixelSize( 238 R.dimen.settings_header_top_margin); 239 } 240 } 241 mHeader.setLayoutParams(params); 242 } 243 } 244