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 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 84 public void onTopEmptyRegionClick() { 85 } 86 87 @Override 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 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 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 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 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