QSPanel.java revision 4bf31983d247a76142937c01bc2800b7f98d0281
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.animation.Animator; 20import android.animation.Animator.AnimatorListener; 21import android.animation.AnimatorListenerAdapter; 22import android.content.Context; 23import android.content.res.Resources; 24import android.os.Handler; 25import android.os.Message; 26import android.util.AttributeSet; 27import android.view.View; 28import android.view.ViewGroup; 29import android.widget.FrameLayout; 30 31import com.android.systemui.R; 32 33import java.util.ArrayList; 34 35/** View that represents the quick settings tile panel. **/ 36public class QSPanel extends ViewGroup { 37 private static final float TILE_ASPECT = 1.2f; 38 39 private final Context mContext; 40 private final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>(); 41 private final FrameLayout mDetail; 42 private final CircularClipper mClipper; 43 private final H mHandler = new H(); 44 45 private int mColumns; 46 private int mCellWidth; 47 private int mCellHeight; 48 private int mLargeCellWidth; 49 private int mLargeCellHeight; 50 51 private TileRecord mDetailRecord; 52 53 public QSPanel(Context context) { 54 this(context, null); 55 } 56 57 public QSPanel(Context context, AttributeSet attrs) { 58 super(context, attrs); 59 mContext = context; 60 61 mDetail = new FrameLayout(mContext); 62 mDetail.setVisibility(GONE); 63 mDetail.setClickable(true); 64 addView(mDetail); 65 mClipper = new CircularClipper(mDetail); 66 updateResources(); 67 } 68 69 public void updateResources() { 70 final Resources res = mContext.getResources(); 71 final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); 72 mCellHeight = res.getDimensionPixelSize(R.dimen.qs_tile_height); 73 mCellWidth = (int)(mCellHeight * TILE_ASPECT); 74 mLargeCellHeight = res.getDimensionPixelSize(R.dimen.qs_dual_tile_height); 75 mLargeCellWidth = (int)(mLargeCellHeight * TILE_ASPECT); 76 if (mColumns != columns) { 77 mColumns = columns; 78 postInvalidate(); 79 } 80 } 81 82 public void setUtils(CircularClipper.Utils utils) { 83 mClipper.setUtils(utils); 84 } 85 86 public void setExpanded(boolean expanded) { 87 if (!expanded) { 88 showDetail(false /*show*/, mDetailRecord); 89 } 90 for (TileRecord r : mRecords) { 91 r.tile.setListening(expanded); 92 if (expanded) { 93 r.tile.refreshState(); 94 } 95 } 96 } 97 98 private void showDetail(boolean show, TileRecord r) { 99 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget(); 100 } 101 102 private void setTileVisibility(View v, boolean visible) { 103 mHandler.obtainMessage(H.SET_TILE_VISIBILITY, visible ? 1 : 0, 0, v).sendToTarget(); 104 } 105 106 private void handleSetTileVisibility(View v, boolean visible) { 107 v.setVisibility(visible ? VISIBLE : GONE); 108 } 109 110 public void addTile(final QSTile<?> tile) { 111 final TileRecord r = new TileRecord(); 112 r.tile = tile; 113 r.tileView = tile.createTileView(mContext); 114 r.tileView.setVisibility(View.GONE); 115 r.tile.setCallback(new QSTile.Callback() { 116 @Override 117 public void onStateChanged(QSTile.State state) { 118 setTileVisibility(r.tileView, state.visible); 119 r.tileView.onStateChanged(state); 120 } 121 @Override 122 public void onShowDetail(boolean show) { 123 QSPanel.this.showDetail(show, r); 124 } 125 }); 126 final View.OnClickListener click = new View.OnClickListener() { 127 @Override 128 public void onClick(View v) { 129 r.tile.click(); 130 } 131 }; 132 final View.OnClickListener clickSecondary = new View.OnClickListener() { 133 @Override 134 public void onClick(View v) { 135 r.tile.secondaryClick(); 136 } 137 }; 138 r.tileView.init(click, clickSecondary); 139 r.tile.refreshState(); 140 mRecords.add(r); 141 142 addView(r.tileView); 143 } 144 145 private void handleShowDetail(TileRecord r, boolean show) { 146 AnimatorListener listener = null; 147 if (show) { 148 if (mDetailRecord != null) return; 149 final View detail = r.tile.createDetailView(mContext, mDetail); 150 if (detail == null) return; 151 mDetailRecord = r; 152 mDetail.removeAllViews(); 153 mDetail.bringToFront(); 154 mDetail.addView(detail); 155 } else { 156 if (mDetailRecord == null) return; 157 listener = mTeardownDetailWhenDone; 158 } 159 int x = r.tileView.getLeft() + r.tileView.getWidth() / 2; 160 int y = r.tileView.getTop() + r.tileView.getHeight() / 2; 161 mClipper.animateCircularClip(x, y, show, listener); 162 } 163 164 @Override 165 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 166 final int width = MeasureSpec.getSize(widthMeasureSpec); 167 int r = -1; 168 int c = -1; 169 int rows = 0; 170 boolean rowIsDual = false; 171 for (TileRecord record : mRecords) { 172 if (record.tileView.getVisibility() == GONE) continue; 173 // wrap to next column if we've reached the max # of columns 174 // also don't allow dual + single tiles on the same row 175 if (r == -1 || c == (mColumns - 1) || rowIsDual != record.tile.supportsDualTargets()) { 176 r++; 177 c = 0; 178 rowIsDual = record.tile.supportsDualTargets(); 179 } else { 180 c++; 181 } 182 record.row = r; 183 record.col = c; 184 rows = r + 1; 185 } 186 187 for (TileRecord record : mRecords) { 188 if (record.tileView.getVisibility() == GONE) continue; 189 record.tileView.setDual(record.tile.supportsDualTargets()); 190 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth; 191 final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight; 192 record.tileView.measure(exactly(cw), exactly(ch)); 193 } 194 final int actualHeight = rows == 0 ? 0 : getRowTop(rows); 195 mDetail.measure(exactly(width), exactly(actualHeight)); 196 setMeasuredDimension(width, actualHeight); 197 } 198 199 private static int exactly(int size) { 200 return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); 201 } 202 203 @Override 204 protected void onLayout(boolean changed, int l, int t, int r, int b) { 205 final int w = getWidth(); 206 for (TileRecord record : mRecords) { 207 if (record.tileView.getVisibility() == GONE) continue; 208 final int cols = getColumnCount(record.row); 209 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth; 210 final int extra = (w - cw * cols) / (cols + 1); 211 final int left = record.col * cw + (record.col + 1) * extra; 212 final int top = getRowTop(record.row); 213 record.tileView.layout(left, top, 214 left + record.tileView.getMeasuredWidth(), 215 top + record.tileView.getMeasuredHeight()); 216 } 217 mDetail.layout(0, 0, mDetail.getMeasuredWidth(), mDetail.getMeasuredHeight()); 218 } 219 220 private int getRowTop(int row) { 221 if (row <= 0) return 0; 222 return mLargeCellHeight + (row - 1) * mCellHeight; 223 } 224 225 private int getColumnCount(int row) { 226 int cols = 0; 227 for (TileRecord record : mRecords) { 228 if (record.tileView.getVisibility() == GONE) continue; 229 if (record.row == row) cols++; 230 } 231 return cols; 232 } 233 234 private class H extends Handler { 235 private static final int SHOW_DETAIL = 1; 236 private static final int SET_TILE_VISIBILITY = 2; 237 @Override 238 public void handleMessage(Message msg) { 239 if (msg.what == SHOW_DETAIL) { 240 handleShowDetail((TileRecord)msg.obj, msg.arg1 != 0); 241 } else if (msg.what == SET_TILE_VISIBILITY) { 242 handleSetTileVisibility((View)msg.obj, msg.arg1 != 0); 243 } 244 } 245 } 246 247 private static final class TileRecord { 248 QSTile<?> tile; 249 QSTileView tileView; 250 int row; 251 int col; 252 } 253 254 private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() { 255 public void onAnimationEnd(Animator animation) { 256 mDetail.removeAllViews(); 257 mDetailRecord = null; 258 }; 259 }; 260} 261