QSTileView.java revision 8600534df66c2ff5846ed230b50c56229322d48a
1/* 2 * Copyright (C) 2014 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 17package com.android.systemui.qs; 18 19import android.content.Context; 20import android.content.res.Resources; 21import android.content.res.TypedArray; 22import android.graphics.Typeface; 23import android.graphics.drawable.Drawable; 24import android.graphics.drawable.RippleDrawable; 25import android.os.Handler; 26import android.os.Looper; 27import android.os.Message; 28import android.util.TypedValue; 29import android.view.ContextThemeWrapper; 30import android.view.Gravity; 31import android.view.View; 32import android.view.ViewGroup; 33import android.widget.ImageView; 34import android.widget.ImageView.ScaleType; 35import android.widget.TextView; 36 37import com.android.systemui.R; 38import com.android.systemui.qs.QSTile.State; 39 40/** View that represents a standard quick settings tile. **/ 41public class QSTileView extends ViewGroup { 42 private static final Typeface CONDENSED = Typeface.create("sans-serif-condensed", 43 Typeface.NORMAL); 44 45 protected final Context mContext; 46 private final View mIcon; 47 private final View mDivider; 48 private final H mHandler = new H(); 49 private final int mIconSizePx; 50 51 private int mTilePaddingPx; 52 private TextView mLabel; 53 private boolean mDual; 54 private OnClickListener mClickPrimary; 55 private OnClickListener mClickSecondary; 56 private RippleDrawable mRipple; 57 58 public QSTileView(Context context) { 59 super(context); 60 61 mContext = context; 62 final Resources res = context.getResources(); 63 mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_tile_icon_size); 64 recreateLabel(); 65 setClipChildren(false); 66 67 mIcon = createIcon(); 68 addView(mIcon); 69 70 mDivider = new View(mContext); 71 mDivider.setBackgroundColor(res.getColor(R.color.qs_tile_divider)); 72 final int dh = res.getDimensionPixelSize(R.dimen.qs_tile_divider_height); 73 mDivider.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, dh)); 74 addView(mDivider); 75 76 setClickable(true); 77 setBackground(getTileBackground()); 78 } 79 80 private void recreateLabel() { 81 CharSequence labelText = null; 82 if (mLabel != null) { 83 labelText = mLabel.getText(); 84 removeView(mLabel); 85 } 86 final Resources res = mContext.getResources(); 87 mLabel = new TextView(mDual 88 ? new ContextThemeWrapper(mContext, R.style.BorderlessButton_Tiny) 89 : mContext); 90 mLabel.setId(android.R.id.title); 91 mLabel.setTextColor(res.getColor(R.color.qs_tile_text)); 92 mLabel.setGravity(Gravity.CENTER_HORIZONTAL); 93 mLabel.setMinLines(2); 94 mTilePaddingPx = res.getDimensionPixelSize( 95 mDual ? R.dimen.qs_dual_tile_padding : R.dimen.qs_tile_padding); 96 final int bottomPadding = mDual ? 0 : mTilePaddingPx; 97 mLabel.setPadding(mTilePaddingPx, mTilePaddingPx, mTilePaddingPx, bottomPadding); 98 mLabel.setTypeface(CONDENSED); 99 mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, 100 res.getDimensionPixelSize(R.dimen.qs_tile_text_size)); 101 if (labelText != null) { 102 mLabel.setText(labelText); 103 } 104 addView(mLabel); 105 } 106 107 public void setDual(boolean dual) { 108 final boolean changed = dual != mDual; 109 mDual = dual; 110 if (changed) { 111 recreateLabel(); 112 } 113 if (mDual) { 114 setOnClickListener(mClickPrimary); 115 mLabel.setClickable(true); 116 mLabel.setOnClickListener(mClickSecondary); 117 } else { 118 mLabel.setClickable(false); 119 setOnClickListener(mClickPrimary); 120 } 121 mDivider.setVisibility(dual ? VISIBLE : GONE); 122 postInvalidate(); 123 } 124 125 public void init(OnClickListener clickPrimary, OnClickListener clickSecondary) { 126 mClickPrimary = clickPrimary; 127 mClickSecondary = clickSecondary; 128 } 129 130 protected View createIcon() { 131 final ImageView icon = new ImageView(mContext); 132 icon.setId(android.R.id.icon); 133 icon.setScaleType(ScaleType.CENTER_INSIDE); 134 return icon; 135 } 136 137 private Drawable getTileBackground() { 138 final int[] attrs = new int[] { android.R.attr.selectableItemBackground}; 139 final TypedArray ta = mContext.obtainStyledAttributes(attrs); 140 final Drawable d = ta.getDrawable(0); 141 ta.recycle(); 142 if (d instanceof RippleDrawable) { 143 mRipple = (RippleDrawable) d; 144 } 145 return d; 146 } 147 148 @Override 149 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 150 final int w = MeasureSpec.getSize(widthMeasureSpec); 151 final int h = MeasureSpec.getSize(heightMeasureSpec); 152 final int iconSpec = exactly(mIconSizePx); 153 mIcon.measure(iconSpec, iconSpec); 154 mLabel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST)); 155 if (mDual) { 156 mDivider.measure(widthMeasureSpec, exactly(mDivider.getLayoutParams().height)); 157 } 158 setMeasuredDimension(w, h); 159 } 160 161 private static int exactly(int size) { 162 return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); 163 } 164 165 @Override 166 protected void onLayout(boolean changed, int l, int t, int r, int b) { 167 final int w = getMeasuredWidth(); 168 final int h = getMeasuredHeight(); 169 170 final int contentHeight = mTilePaddingPx + mIcon.getMeasuredHeight() 171 + mLabel.getMeasuredHeight() 172 + (mDual ? (mTilePaddingPx + mDivider.getMeasuredHeight()) : 0); 173 174 int top = Math.max(0, (h - contentHeight) / 2); 175 top += mTilePaddingPx; 176 final int iconLeft = (w - mIcon.getMeasuredWidth()) / 2; 177 layout(mIcon, iconLeft, top); 178 if (mRipple != null) { 179 // center the touch feedback on the center of the icon, and dial it down a bit 180 final int cx = w / 2; 181 final int cy = mIcon.getTop() + mIcon.getHeight() / 2; 182 final int rad = (int)(mIcon.getHeight() * 1.5); 183 mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad); 184 } 185 top = mIcon.getBottom(); 186 if (mDual) { 187 top += mTilePaddingPx; 188 layout(mDivider, 0, top); 189 top = mDivider.getBottom(); 190 } 191 layout(mLabel, 0, top); 192 } 193 194 private static void layout(View child, int left, int top) { 195 child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight()); 196 } 197 198 protected void handleStateChanged(QSTile.State state) { 199 if (mIcon instanceof ImageView) { 200 ImageView iv = (ImageView) mIcon; 201 if (state.icon != null) { 202 iv.setImageDrawable(state.icon); 203 } else if (state.iconId > 0) { 204 iv.setImageResource(state.iconId); 205 } 206 } 207 mLabel.setText(state.label); 208 setContentDescription(state.contentDescription); 209 } 210 211 public void onStateChanged(QSTile.State state) { 212 mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget(); 213 } 214 215 private class H extends Handler { 216 private static final int STATE_CHANGED = 1; 217 public H() { 218 super(Looper.getMainLooper()); 219 } 220 @Override 221 public void handleMessage(Message msg) { 222 if (msg.what == STATE_CHANGED) { 223 handleStateChanged((State) msg.obj); 224 } 225 } 226 } 227}