QS: Add long-press to customize prototype - part 2

- Add info/remove drop targets that appear during dragging
 - Move drag start into NonPagedTileLayout so that both start
   and end of tile move drags are in the same place.
 - Still needs way to add tiles

Change-Id: If843ebbb86f393b461289c1407c6a82b6a5aed9d
This commit is contained in:
Jason Monk
2015-09-09 13:03:20 -04:00
parent 2fd54a451c
commit f7fe83f36b
8 changed files with 273 additions and 65 deletions

View File

@@ -0,0 +1,24 @@
<!--
Copyright (C) 2015 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
android:fillColor="#FFFFFFFF"/>
</vector>

View File

@@ -21,13 +21,58 @@
android:orientation="vertical"
android:background="?android:attr/windowBackground">
<Toolbar
android:id="@*android:id/action_bar"
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:navigationContentDescription="@*android:string/action_bar_up_description"
android:background="?android:attr/colorPrimary"
style="?android:attr/toolbarStyle" />
android:background="?android:attr/colorPrimary">
<LinearLayout
android:id="@+id/drag_buttons"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<FrameLayout
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1">
<com.android.systemui.qs.customize.DropButton
android:id="@+id/info_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:drawableStart="@drawable/ic_info"
android:drawablePadding="10dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@android:color/white"
android:text="@string/qs_customize_info" />
</FrameLayout>
<FrameLayout
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1">
<com.android.systemui.qs.customize.DropButton
android:id="@+id/remove_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:drawableStart="@drawable/ic_close_white"
android:drawablePadding="10dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@android:color/white"
android:text="@string/qs_customize_remove" />
</FrameLayout>
</LinearLayout>
<Toolbar
android:id="@*android:id/action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:navigationContentDescription="@*android:string/action_bar_up_description"
style="?android:attr/toolbarStyle"
android:background="?android:attr/colorPrimary" />
</FrameLayout>
<com.android.systemui.tuner.AutoScrollView
android:layout_width="match_parent"

View File

@@ -1158,5 +1158,7 @@
<string name="save" translatable="false">Save</string>
<string name="qs_customize" translatable="false">Allow long-press customize in Quick Settings</string>
<string name="qs_customize_info" translatable="false">Info</string>
<string name="qs_customize_remove" translatable="false">Remove</string>
</resources>

View File

@@ -19,26 +19,21 @@ import android.content.ClipData;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTileView;
import com.android.systemui.statusbar.phone.QSTileHost;
/**
* A version of QSPanel that allows tiles to be dragged around rather than
* clicked on. Dragging is started here, receiving is handled in the NonPagedTileLayout,
* clicked on. Dragging starting and receiving is handled in the NonPagedTileLayout,
* and the saving/ordering is handled by the CustomQSTileHost.
*/
public class CustomQSPanel extends QSPanel implements OnTouchListener {
public class CustomQSPanel extends QSPanel {
private CustomQSTileHost mCustomHost;
private ClipData mCurrentClip;
private View mCurrentView;
public CustomQSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -62,52 +57,26 @@ public class CustomQSPanel extends QSPanel implements OnTouchListener {
}
}
@Override
protected void addTile(QSTile<?> tile) {
super.addTile(tile);
if (tile.getTileType() != QSTileView.QS_TYPE_QUICK) {
TileRecord record = mRecords.get(mRecords.size() - 1);
if (record.tileView.getTag() == record.tile) {
return;
}
record.tileView.setTag(record.tile);
record.tileView.setVisibility(View.VISIBLE);
record.tileView.init(null, null, null);
record.tileView.setOnTouchListener(this);
if (mCurrentClip != null
&& mCurrentClip.getItemAt(0).getText().toString().equals(tile.getTileSpec())) {
record.tileView.setAlpha(.3f);
mCurrentView = record.tileView;
}
}
public CustomQSTileHost getCustomHost() {
return mCustomHost;
}
public void tileSelected(View v) {
String sourceSpec = mCurrentClip.getItemAt(0).getText().toString();
String destSpec = ((QSTile<?>) v.getTag()).getTileSpec();
public void tileSelected(QSTile<?> tile, ClipData currentClip) {
String sourceSpec = getSpec(currentClip);
String destSpec = tile.getTileSpec();
if (!sourceSpec.equals(destSpec)) {
mCustomHost.moveTo(sourceSpec, destSpec);
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
String tileSpec = (String) ((QSTile<?>) v.getTag()).getTileSpec();
mCurrentView = v;
mCurrentClip = ClipData.newPlainText(tileSpec, tileSpec);
View.DragShadowBuilder shadow = new View.DragShadowBuilder(v);
((View) getParent().getParent()).startDrag(mCurrentClip, shadow, null, 0);
v.setAlpha(.3f);
return true;
}
return false;
public ClipData getClip(QSTile<?> tile) {
String tileSpec = tile.getTileSpec();
// TODO: Something better than plain text.
// TODO: Once using something better than plain text, stop listening to non-QS drag events.
return ClipData.newPlainText(tileSpec, tileSpec);
}
public void onDragEnded() {
mCurrentView.setAlpha(1f);
mCurrentView = null;
mCurrentClip = null;
public String getSpec(ClipData data) {
return data.getItemAt(0).getText().toString();
}
}

View File

@@ -37,6 +37,7 @@ public class CustomQSTileHost extends QSTileHost {
private static final String TAG = "CustomHost";
private List<String> mTiles;
private List<String> mSavedTiles;
private ArrayList<String> mStash;
public CustomQSTileHost(Context context, QSTileHost host) {
super(context, null, host.getBluetoothController(), host.getLocationController(),
@@ -70,6 +71,14 @@ public class CustomQSTileHost extends QSTileHost {
TextUtils.join(",", mTiles), ActivityManager.getCurrentUser());
}
public void stashCurrentTiles() {
mStash = new ArrayList<>(mTiles);
}
public void unstashTiles() {
setTiles(mStash);
}
public void moveTo(String from, String to) {
int fromIndex = mTiles.indexOf(from);
if (fromIndex < 0) {
@@ -86,6 +95,13 @@ public class CustomQSTileHost extends QSTileHost {
super.onTuningChanged(TILES_SETTING, null);
}
public void remove(String spec) {
if (!mTiles.remove(spec)) {
Log.e(TAG, "Unknown remove spec " + spec);
}
super.onTuningChanged(TILES_SETTING, null);
}
public void setTiles(List<String> tiles) {
mTiles = new ArrayList<>(tiles);
super.onTuningChanged(TILES_SETTING, null);

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.qs.customize;
import android.content.ClipData;
import android.content.Context;
import android.util.AttributeSet;
import android.view.DragEvent;
import android.view.View;
import android.view.View.OnDragListener;
import android.widget.TextView;
public class DropButton extends TextView implements OnDragListener {
private OnDropListener mListener;
public DropButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// TODO: Don't do this, instead make this view the right size...
((View) getParent()).setOnDragListener(this);
}
public void setOnDropListener(OnDropListener listener) {
mListener = listener;
}
private void setHovering(boolean hovering) {
setAlpha(hovering ? .5f : 1);
}
@Override
public boolean onDrag(View v, DragEvent event) {
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_ENTERED:
setHovering(true);
break;
case DragEvent.ACTION_DROP:
if (mListener != null) {
mListener.onDrop(this, event.getClipData());
}
case DragEvent.ACTION_DRAG_EXITED:
case DragEvent.ACTION_DRAG_ENDED:
setHovering(false);
break;
}
return true;
}
public interface OnDropListener {
void onDrop(View v, ClipData data);
}
}

View File

@@ -15,12 +15,15 @@
*/
package com.android.systemui.qs.customize;
import android.content.ClipData;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.DragEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.LinearLayout;
import com.android.systemui.R;
@@ -28,6 +31,7 @@ import com.android.systemui.qs.PagedTileLayout;
import com.android.systemui.qs.PagedTileLayout.TilePage;
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSPanel.TileRecord;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTileView;
import com.android.systemui.qs.QuickTileLayout;
@@ -38,7 +42,7 @@ import java.util.ArrayList;
* vertically and expects to be inside a ScrollView.
* @see CustomQSPanel
*/
public class NonPagedTileLayout extends LinearLayout implements QSTileLayout {
public class NonPagedTileLayout extends LinearLayout implements QSTileLayout, OnTouchListener {
private QuickTileLayout mQuickTiles;
private final ArrayList<TilePage> mPages = new ArrayList<>();
@@ -46,6 +50,9 @@ public class NonPagedTileLayout extends LinearLayout implements QSTileLayout {
private CustomQSPanel mPanel;
private final Rect mHitRect = new Rect();
private ClipData mCurrentClip;
private View mCurrentView;
public NonPagedTileLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -63,17 +70,23 @@ public class NonPagedTileLayout extends LinearLayout implements QSTileLayout {
mPanel = qsPanel;
}
private void clear() {
mQuickTiles.removeAllViews();
for (int i = 0; i < mPages.size(); i++) {
mPages.get(i).removeAllViews();
}
}
@Override
public void addTile(TileRecord tile) {
mTiles.add(tile);
public void addTile(TileRecord record) {
mTiles.add(record);
distributeTiles();
if (record.tile.getTileType() == QSTileView.QS_TYPE_QUICK
|| record.tileView.getTag() == record.tile) {
return;
}
record.tileView.setTag(record.tile);
record.tileView.setVisibility(View.VISIBLE);
record.tileView.init(null, null, null);
record.tileView.setOnTouchListener(this);
if (mCurrentClip != null
&& mCurrentClip.getItemAt(0).getText().toString().equals(record.tile.getTileSpec())) {
record.tileView.setAlpha(.3f);
mCurrentView = record.tileView;
}
}
@Override
@@ -118,8 +131,8 @@ public class NonPagedTileLayout extends LinearLayout implements QSTileLayout {
@Override
public int getOffsetTop(TileRecord tile) {
// TODO: Fix this.
return getTop();
// No touch feedback, so this isn't required.
return 0;
}
@Override
@@ -145,7 +158,7 @@ public class NonPagedTileLayout extends LinearLayout implements QSTileLayout {
for (int j = 0; j < NC; j++) {
View child = page.getChildAt(j);
if (contains(child, x, y)) {
mPanel.tileSelected(child);
mPanel.tileSelected((QSTile<?>) child.getTag(), mCurrentClip);
}
}
break;
@@ -154,12 +167,35 @@ public class NonPagedTileLayout extends LinearLayout implements QSTileLayout {
}
break;
case DragEvent.ACTION_DRAG_ENDED:
mPanel.onDragEnded();
onDragEnded();
break;
}
return true;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Stash the current tiles, in case the drop is on info, that we we can restore
// the previous state.
mPanel.getCustomHost().stashCurrentTiles();
mCurrentView = v;
mCurrentClip = mPanel.getClip((QSTile<?>) v.getTag());
View.DragShadowBuilder shadow = new View.DragShadowBuilder(v);
((View) getParent().getParent()).startDrag(mCurrentClip, shadow, null, 0);
v.setAlpha(.3f);
return true;
}
return false;
}
public void onDragEnded() {
mCurrentView.setAlpha(1f);
mCurrentView = null;
mCurrentClip = null;
}
private boolean contains(View v, float x, float y) {
v.getHitRect(mHitRect);
return mHitRect.contains((int) x, (int) y);

View File

@@ -15,13 +15,17 @@
*/
package com.android.systemui.qs.customize;
import android.app.AlertDialog;
import android.content.ClipData;
import android.content.Context;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.DragEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Toolbar;
import android.widget.Toolbar.OnMenuItemClickListener;
@@ -29,8 +33,10 @@ import android.widget.Toolbar.OnMenuItemClickListener;
import com.android.systemui.R;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.qs.QSTile.Host.Callback;
import com.android.systemui.qs.customize.DropButton.OnDropListener;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.tuner.QSPagingSwitch;
import java.util.ArrayList;
@@ -41,7 +47,8 @@ import java.util.ArrayList;
* This adds itself to the status bar window, so it can appear on top of quick settings and
* *someday* do fancy animations to get into/out of it.
*/
public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener, Callback {
public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener, Callback,
OnDropListener {
private static final int MENU_SAVE = Menu.FIRST;
private static final int MENU_RESET = Menu.FIRST + 1;
@@ -49,10 +56,13 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
private PhoneStatusBar mPhoneStatusBar;
private Toolbar mToolbar;
private ViewGroup mDragButtons;
private CustomQSPanel mQsPanel;
private boolean isShown;
private CustomQSTileHost mHost;
private DropButton mInfoButton;
private DropButton mRemoveButton;
public QSCustomizer(Context context, AttributeSet attrs) {
super(new ContextThemeWrapper(context, android.R.style.Theme_Material), attrs);
@@ -90,6 +100,14 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
mContext.getString(com.android.internal.R.string.reset));
mQsPanel = (CustomQSPanel) findViewById(R.id.quick_settings_panel);
mDragButtons = (ViewGroup) findViewById(R.id.drag_buttons);
setDragging(false);
mInfoButton = (DropButton) findViewById(R.id.info_button);
mInfoButton.setOnDropListener(this);
mRemoveButton = (DropButton) findViewById(R.id.remove_button);
mRemoveButton.setOnDropListener(this);
}
public void show() {
@@ -117,6 +135,10 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
mHost.setTiles(tiles);
}
private void setDragging(boolean dragging) {
mToolbar.setVisibility(!dragging ? View.VISIBLE : View.INVISIBLE);
}
private void save() {
mHost.saveCurrentTiles();
hide();
@@ -139,4 +161,28 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
public void onTilesChanged() {
mQsPanel.setTiles(mHost.getTiles());
}
public boolean onDragEvent(DragEvent event) {
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
setDragging(true);
break;
case DragEvent.ACTION_DRAG_ENDED:
setDragging(false);
break;
}
return true;
}
public void onDrop(View v, ClipData data) {
if (v == mRemoveButton) {
mHost.remove(mQsPanel.getSpec(data));
} else if (v == mInfoButton) {
mHost.unstashTiles();
SystemUIDialog dialog = new SystemUIDialog(mContext);
dialog.setTitle(mQsPanel.getSpec(data));
dialog.setPositiveButton(R.string.ok, null);
dialog.show();
}
}
}