Show the see more item when scrolling is disabled on long list
* Added to wifi sample / render test to show list of fake networks, it
also implements see more
* Logic for determining how many list items we can show for the height
when scrolling is disabled (effectively disabling scrolling on the RV)
* Some adjustments to measuring / heights to support this logic
Bug: 68378574
Test: ./gradlew slices-view:connectedCheck (check wifi slice)
Change-Id: I0cbfa04faa6d2c02f587e40b9ad4b4244f35e388
diff --git a/slices/view/src/androidTest/java/androidx/app/slice/render/SliceCreator.java b/slices/view/src/androidTest/java/androidx/app/slice/render/SliceCreator.java
index 546f1e3..5e91022 100644
--- a/slices/view/src/androidTest/java/androidx/app/slice/render/SliceCreator.java
+++ b/slices/view/src/androidTest/java/androidx/app/slice/render/SliceCreator.java
@@ -48,7 +48,7 @@
"com.example.androidx.slice.action.TOAST";
public static final String EXTRA_TOAST_MESSAGE = "com.example.androidx.extra.TOAST_MESSAGE";
- public static final String[] URI_PATHS = {"message", "wifi", "note", "ride", "toggle",
+ public static final String[] URI_PATHS = {"message", "wifi", "wifi2", "note", "ride", "toggle",
"toggle2", "contact", "gallery", "weather"};
private final Context mContext;
@@ -78,7 +78,9 @@
case "/message":
return createMessagingSlice(sliceUri);
case "/wifi":
- return createWifiSlice(sliceUri);
+ return createWifiSlice(sliceUri, false /* customSeeMore */);
+ case "/wifi2":
+ return createWifiSlice(sliceUri, true /* customSeeMore */);
case "/note":
return createNoteSlice(sliceUri);
case "/ride":
@@ -283,7 +285,7 @@
.build();
}
- private Slice createWifiSlice(Uri sliceUri) {
+ private Slice createWifiSlice(Uri sliceUri, boolean customSeeMore) {
// Get wifi state
WifiManager wifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
int wifiState = wifiManager.getWifiState();
@@ -305,18 +307,46 @@
break;
}
boolean finalWifiEnabled = wifiEnabled;
- ListBuilder b = new ListBuilder(getContext(), sliceUri);
+ ListBuilder lb = new ListBuilder(getContext(), sliceUri);
SliceAction primaryAction = new SliceAction(getIntent(Settings.ACTION_WIFI_SETTINGS),
Icon.createWithResource(getContext(), R.drawable.ic_wifi), "Wi-fi Settings");
- return b.setColor(0xff4285f4)
- .addRow(new ListBuilder.RowBuilder(b)
- .setTitle("Wi-fi")
- .setTitleItem(Icon.createWithResource(getContext(), R.drawable.ic_wifi))
- .setSubtitle(state)
- .addEndItem(new SliceAction(getBroadcastIntent(ACTION_WIFI_CHANGED, null),
- "Toggle wifi", finalWifiEnabled))
- .setPrimaryAction(primaryAction))
- .build();
+ lb.setColor(0xff4285f4);
+ lb.addRow(new ListBuilder.RowBuilder(lb)
+ .setTitle("Wi-fi")
+ .setTitleItem(Icon.createWithResource(getContext(), R.drawable.ic_wifi))
+ .setSubtitle(state)
+ .addEndItem(new SliceAction(getBroadcastIntent(ACTION_WIFI_CHANGED, null),
+ "Toggle wifi", finalWifiEnabled))
+ .setPrimaryAction(primaryAction));
+
+ // Add fake wifi networks
+ int[] wifiIcons = new int[] {R.drawable.ic_wifi_full, R.drawable.ic_wifi_low,
+ R.drawable.ic_wifi_fair};
+ for (int i = 0; i < 10; i++) {
+ final int iconId = wifiIcons[i % wifiIcons.length];
+ Icon icon = Icon.createWithResource(getContext(), iconId);
+ final String networkName = "Network" + i;
+ ListBuilder.RowBuilder rb = new ListBuilder.RowBuilder(lb);
+ rb.setTitleItem(icon)
+ .setTitle("Network" + networkName);
+ boolean locked = i % 3 == 0;
+ if (locked) {
+ rb.addEndItem(Icon.createWithResource(getContext(), R.drawable.ic_lock));
+ }
+ String message = locked ? "Open wifi password dialog" : "Connect to " + networkName;
+ rb.setPrimaryAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, message), icon,
+ message));
+ lb.addRow(rb);
+ }
+ if (customSeeMore) {
+ lb.addSeeMoreRow(new ListBuilder.RowBuilder(lb)
+ .setTitle("See all available networks")
+ .addEndItem(Icon.createWithResource(getContext(), R.drawable.ic_right_caret))
+ .setPrimaryAction(primaryAction));
+ } else {
+ lb.addSeeMoreAction(primaryAction.getAction());
+ }
+ return lb.build();
}
private PendingIntent getIntent(String action) {
diff --git a/slices/view/src/androidTest/java/androidx/app/slice/render/SliceRenderer.java b/slices/view/src/androidTest/java/androidx/app/slice/render/SliceRenderer.java
index dc9196e..067e4c7 100644
--- a/slices/view/src/androidTest/java/androidx/app/slice/render/SliceRenderer.java
+++ b/slices/view/src/androidTest/java/androidx/app/slice/render/SliceRenderer.java
@@ -127,7 +127,13 @@
}
mDoneLatch = new CountDownLatch(SliceCreator.URI_PATHS.length);
for (String slice : SliceCreator.URI_PATHS) {
- doRender(slice, new File(output, String.format("%s.png", slice)));
+ doRender(slice, new File(output, String.format("%s.png", slice)),
+ true /* scrollable */);
+ if (slice.equals("wifi") || slice.equals("wifi2")) {
+ // Test scrolling
+ doRender(slice, new File(output, String.format("%s-no-scroll.png", slice)),
+ false /* scrollable */);
+ }
}
Log.d(TAG, "Wrote render to " + output.getAbsolutePath());
mContext.runOnUiThread(new Runnable() {
@@ -142,7 +148,7 @@
}
}
- private void doRender(final String slice, final File file) {
+ private void doRender(final String slice, final File file, final boolean scrollable) {
Log.d(TAG, "Rendering " + slice + " to " + file.getAbsolutePath());
final Slice s = mSliceCreator.onBindSlice(SliceCreator.getUri(slice, mContext));
@@ -154,6 +160,7 @@
mSV1.setSlice(s);
mSV2.setSlice(s);
mSV3.setSlice(s);
+ mSV3.setScrollable(scrollable);
mSV1.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
diff --git a/slices/view/src/androidTest/res/drawable/ic_lock.xml b/slices/view/src/androidTest/res/drawable/ic_lock.xml
new file mode 100644
index 0000000..7a79428
--- /dev/null
+++ b/slices/view/src/androidTest/res/drawable/ic_lock.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright 2018 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 android:height="16dp" android:viewportHeight="24.0"
+ android:viewportWidth="24.0" android:width="16dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#FF000000" android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1s3.1,1.39 3.1,3.1v2L8.9,8L8.9,6zM18,20L6,20L6,10h12v10z"/>
+</vector>
diff --git a/slices/view/src/androidTest/res/drawable/ic_right_caret.xml b/slices/view/src/androidTest/res/drawable/ic_right_caret.xml
new file mode 100644
index 0000000..3310e62
--- /dev/null
+++ b/slices/view/src/androidTest/res/drawable/ic_right_caret.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright 2018 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="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
+</vector>
diff --git a/slices/view/src/androidTest/res/drawable/ic_wifi_fair.xml b/slices/view/src/androidTest/res/drawable/ic_wifi_fair.xml
new file mode 100644
index 0000000..4e92a64
--- /dev/null
+++ b/slices/view/src/androidTest/res/drawable/ic_wifi_fair.xml
@@ -0,0 +1,29 @@
+<!--
+ ~ Copyright 2018 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="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3.53,10.95l8.46,10.54 0.01,0.01 0.01,-0.01 8.46,-10.54C20.04,10.62 16.81,8 12,8c-4.81,0 -8.04,2.62 -8.47,2.95z"/>
+</vector>
diff --git a/slices/view/src/androidTest/res/drawable/ic_wifi_full.xml b/slices/view/src/androidTest/res/drawable/ic_wifi_full.xml
new file mode 100644
index 0000000..0b37310
--- /dev/null
+++ b/slices/view/src/androidTest/res/drawable/ic_wifi_full.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright 2018 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="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"/>
+</vector>
diff --git a/slices/view/src/androidTest/res/drawable/ic_wifi_low.xml b/slices/view/src/androidTest/res/drawable/ic_wifi_low.xml
new file mode 100644
index 0000000..87c84ce
--- /dev/null
+++ b/slices/view/src/androidTest/res/drawable/ic_wifi_low.xml
@@ -0,0 +1,29 @@
+<!--
+ ~ Copyright 2018 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="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"
+ android:fillAlpha=".3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.67,14.86L12,21.49v0.01l0.01,-0.01 5.33,-6.63C17.06,14.65 15.03,13 12,13s-5.06,1.65 -5.33,1.86z"/>
+</vector>
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/LargeTemplateView.java b/slices/view/src/main/java/androidx/app/slice/widget/LargeTemplateView.java
index bdd1ac5..4ab6d0c 100644
--- a/slices/view/src/main/java/androidx/app/slice/widget/LargeTemplateView.java
+++ b/slices/view/src/main/java/androidx/app/slice/widget/LargeTemplateView.java
@@ -23,6 +23,7 @@
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
+import java.util.ArrayList;
import java.util.List;
import androidx.app.slice.Slice;
@@ -40,6 +41,8 @@
private Slice mSlice;
private boolean mIsScrollable;
private ListContent mListContent;
+ private List<SliceItem> mDisplayedItems = new ArrayList<>();
+ private int mDisplayedItemsHeight = 0;
public LargeTemplateView(Context context) {
super(context);
@@ -51,8 +54,18 @@
}
@Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ if (mDisplayedItems.size() > 0 && mDisplayedItemsHeight > height) {
+ // Need to resize
+ updateDisplayedItems(height);
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
public int getActualHeight() {
- return mListContent != null ? mListContent.getListHeight() : 0;
+ return mDisplayedItemsHeight;
}
@Override
@@ -93,23 +106,46 @@
private void populate() {
if (mSlice == null) {
+ resetView();
return;
}
mListContent = new ListContent(getContext(), mSlice);
- mAdapter.setSliceItems(mListContent.getRowItems(), mTintColor);
+ updateDisplayedItems(getMeasuredHeight());
}
/**
* Whether or not the content in this template should be scrollable.
*/
public void setScrollable(boolean isScrollable) {
- // TODO -- restrict / enable how much this view can show
mIsScrollable = isScrollable;
+ updateDisplayedItems(getMeasuredHeight());
+ }
+
+ private void updateDisplayedItems(int height) {
+ if (mListContent == null) {
+ return;
+ }
+ if (!mIsScrollable) {
+ // If we're not scrollable we must cap the number of items we're displaying such
+ // that they fit in the available space
+ if (height == 0) {
+ // Not measured, use default
+ mDisplayedItems = mListContent.getItemsForNonScrollingList(-1);
+ } else {
+ mDisplayedItems = mListContent.getItemsForNonScrollingList(height);
+ }
+ } else {
+ mDisplayedItems = mListContent.getRowItems();
+ }
+ mDisplayedItemsHeight = ListContent.getListHeight(getContext(), mDisplayedItems);
+ mAdapter.setSliceItems(mDisplayedItems, mTintColor);
}
@Override
public void resetView() {
mSlice = null;
+ mDisplayedItemsHeight = 0;
+ mDisplayedItems.clear();
mAdapter.setSliceItems(null, -1);
mListContent = null;
}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/ListContent.java b/slices/view/src/main/java/androidx/app/slice/widget/ListContent.java
index 246ef0b..d280fe8 100644
--- a/slices/view/src/main/java/androidx/app/slice/widget/ListContent.java
+++ b/slices/view/src/main/java/androidx/app/slice/widget/ListContent.java
@@ -19,6 +19,7 @@
import static android.app.slice.Slice.HINT_ACTIONS;
import static android.app.slice.Slice.HINT_HORIZONTAL;
import static android.app.slice.Slice.HINT_LIST_ITEM;
+import static android.app.slice.Slice.HINT_SEE_MORE;
import static android.app.slice.Slice.HINT_SHORTCUT;
import static android.app.slice.Slice.SUBTYPE_COLOR;
import static android.app.slice.SliceItem.FORMAT_ACTION;
@@ -48,6 +49,7 @@
private SliceItem mHeaderItem;
private SliceItem mColorItem;
+ private SliceItem mSeeMoreItem;
private ArrayList<SliceItem> mRowItems = new ArrayList<>();
private List<SliceItem> mSliceActions;
private Context mContext;
@@ -79,12 +81,13 @@
if (mHeaderItem != null) {
mRowItems.add(mHeaderItem);
}
+ mSeeMoreItem = getSeeMoreItem(slice);
// Filter + create row items
List<SliceItem> children = slice.getItems();
for (int i = 0; i < children.size(); i++) {
final SliceItem child = children.get(i);
final String format = child.getFormat();
- if (!child.hasAnyHints(HINT_ACTIONS)
+ if (!child.hasAnyHints(HINT_ACTIONS, HINT_SEE_MORE)
&& (FORMAT_ACTION.equals(format) || FORMAT_SLICE.equals(format))) {
if (mHeaderItem == null && !child.hasHint(HINT_LIST_ITEM)) {
mHeaderItem = child;
@@ -102,24 +105,70 @@
}
/**
- * @return the total height of all the rows contained in this list.
+ * @return the total height of all the rows contained in the provided list.
*/
- public int getListHeight() {
+ public static int getListHeight(Context context, List<SliceItem> listItems) {
int height = 0;
- for (int i = 0; i < mRowItems.size(); i++) {
- SliceItem item = mRowItems.get(i);
- if (item.hasHint(HINT_HORIZONTAL)) {
- GridContent gc = new GridContent(mContext, item);
- height += gc.getActualHeight();
- } else {
- RowContent rc = new RowContent(mContext, item, i == 0 /* isHeader */);
- height += rc.getActualHeight();
- }
+ for (int i = 0; i < listItems.size(); i++) {
+ height += getHeight(context, listItems.get(i), i == 0 /* isHeader */);
}
return height;
}
/**
+ * Returns a list of items that can be displayed in the provided height. If this list
+ * has a {@link #getSeeMoreItem()} this will be returned in the list if appropriate.
+ *
+ * @param height the height to restrict the items, -1 to use default sizings for non-scrolling
+ * templates.
+ * @return the list of items that can be displayed in the provided height.
+ */
+ @NonNull
+ public List<SliceItem> getItemsForNonScrollingList(int height) {
+ ArrayList<SliceItem> visibleItems = new ArrayList<>();
+ if (mRowItems == null || mRowItems.size() == 0) {
+ return visibleItems;
+ }
+ final int idealItemCount = hasHeader() ? 4 : 3;
+ final int minItemCount = hasHeader() ? 2 : 1;
+ int visibleHeight = 0;
+ // Need to show see more
+ if (mSeeMoreItem != null) {
+ RowContent rc = new RowContent(mContext, mSeeMoreItem, false /* isHeader */);
+ visibleHeight += rc.getActualHeight();
+ }
+ for (int i = 0; i < mRowItems.size(); i++) {
+ int itemHeight = getHeight(mContext, mRowItems.get(i), i == 0 /* isHeader */);
+ if ((height == -1 && i > idealItemCount)
+ || (height > 0 && visibleHeight + itemHeight > height)) {
+ break;
+ } else {
+ visibleHeight += itemHeight;
+ visibleItems.add(mRowItems.get(i));
+ }
+ }
+ if (mSeeMoreItem != null && visibleItems.size() >= minItemCount) {
+ // Only add see more if we're at least showing one item and it's not the header
+ visibleItems.add(mSeeMoreItem);
+ }
+ if (visibleItems.size() == 0) {
+ // Didn't have enough space to show anything; should still show something
+ visibleItems.add(mRowItems.get(0));
+ }
+ return visibleItems;
+ }
+
+ private static int getHeight(Context context, SliceItem item, boolean isHeader) {
+ if (item.hasHint(HINT_HORIZONTAL)) {
+ GridContent gc = new GridContent(context, item);
+ return gc.getActualHeight();
+ } else {
+ RowContent rc = new RowContent(context, item, isHeader);
+ return rc.getActualHeight();
+ }
+ }
+
+ /**
* @return whether this list has content that is valid to display.
*/
public boolean isValid() {
@@ -141,6 +190,11 @@
return mSliceActions;
}
+ @Nullable
+ public SliceItem getSeeMoreItem() {
+ return mSeeMoreItem;
+ }
+
public ArrayList<SliceItem> getRowItems() {
return mRowItems;
}
@@ -163,6 +217,21 @@
return null;
}
+ @Nullable
+ private static SliceItem getSeeMoreItem(@NonNull Slice slice) {
+ SliceItem item = SliceQuery.find(slice, null, HINT_SEE_MORE, null);
+ if (item != null && item.hasHint(HINT_SEE_MORE)) {
+ if (FORMAT_SLICE.equals(item.getFormat())) {
+ List<SliceItem> items = item.getSlice().getItems();
+ if (items.size() == 1 && FORMAT_ACTION.equals(items.get(0).getFormat())) {
+ return items.get(0);
+ }
+ return item;
+ }
+ }
+ return null;
+ }
+
private static boolean isValidHeader(SliceItem sliceItem) {
if (FORMAT_SLICE.equals(sliceItem.getFormat()) && !sliceItem.hasHint(HINT_LIST_ITEM)
&& !sliceItem.hasHint(HINT_ACTIONS)) {
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/RowContent.java b/slices/view/src/main/java/androidx/app/slice/widget/RowContent.java
index a22a37d..0981d06 100644
--- a/slices/view/src/main/java/androidx/app/slice/widget/RowContent.java
+++ b/slices/view/src/main/java/androidx/app/slice/widget/RowContent.java
@@ -17,6 +17,7 @@
package androidx.app.slice.widget;
import static android.app.slice.Slice.HINT_ACTIONS;
+import static android.app.slice.Slice.HINT_SEE_MORE;
import static android.app.slice.Slice.HINT_SHORTCUT;
import static android.app.slice.Slice.HINT_SUMMARY;
import static android.app.slice.Slice.HINT_TITLE;
@@ -31,6 +32,7 @@
import static androidx.app.slice.core.SliceHints.SUBTYPE_RANGE;
import android.content.Context;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.text.TextUtils;
@@ -52,6 +54,7 @@
private static final String TAG = "RowContent";
private SliceItem mPrimaryAction;
+ private SliceItem mRowSlice;
private SliceItem mStartItem;
private SliceItem mTitleItem;
private SliceItem mSubtitleItem;
@@ -75,6 +78,7 @@
*/
public void reset() {
mPrimaryAction = null;
+ mRowSlice = null;
mStartItem = null;
mTitleItem = null;
mSubtitleItem = null;
@@ -89,6 +93,7 @@
private boolean populate(SliceItem rowSlice, boolean isHeader) {
reset();
mIsHeader = isHeader;
+ mRowSlice = rowSlice;
if (!isValidRow(rowSlice)) {
Log.w(TAG, "Provided SliceItem is invalid for RowContent");
return false;
@@ -173,6 +178,14 @@
}
/**
+ * @return the {@link SliceItem} used to populate this row.
+ */
+ @NonNull
+ public SliceItem getSlice() {
+ return mRowSlice;
+ }
+
+ /**
* @return the {@link SliceItem} representing the range in the row; can be null.
*/
@Nullable
@@ -181,30 +194,32 @@
}
/**
- * @return whether this row has content that is valid to display.
+ * @return the {@link SliceItem} used for the main intent for this row; can be null.
*/
- public boolean isValid() {
- return mStartItem != null
- || mTitleItem != null
- || mSubtitleItem != null
- || mEndItems.size() > 0;
- }
-
@Nullable
public SliceItem getPrimaryAction() {
return mPrimaryAction;
}
+ /**
+ * @return the {@link SliceItem} to display at the start of this row; can be null.
+ */
@Nullable
public SliceItem getStartItem() {
return mIsHeader ? null : mStartItem;
}
+ /**
+ * @return the {@link SliceItem} representing the title text for this row; can be null.
+ */
@Nullable
public SliceItem getTitleItem() {
return mTitleItem;
}
+ /**
+ * @return the {@link SliceItem} representing the subtitle text for this row; can be null.
+ */
@Nullable
public SliceItem getSubtitleItem() {
return mSubtitleItem;
@@ -215,6 +230,9 @@
return mSummaryItem == null ? mSubtitleItem : mSummaryItem;
}
+ /**
+ * @return the list of {@link SliceItem} that can be shown as items at the end of the row.
+ */
public ArrayList<SliceItem> getEndItems() {
return mEndItems;
}
@@ -254,6 +272,26 @@
}
/**
+ * @return whether this row content represents a default see more item.
+ */
+ public boolean isDefaultSeeMore() {
+ return FORMAT_ACTION.equals(mRowSlice.getFormat())
+ && mRowSlice.getSlice().hasHint(HINT_SEE_MORE)
+ && mRowSlice.getSlice().getItems().isEmpty();
+ }
+
+ /**
+ * @return whether this row has content that is valid to display.
+ */
+ public boolean isValid() {
+ return mStartItem != null
+ || mTitleItem != null
+ || mSubtitleItem != null
+ || mEndItems.size() > 0
+ || isDefaultSeeMore();
+ }
+
+ /**
* @return whether this is a valid item to use to populate a row of content.
*/
private static boolean isValidRow(SliceItem rowSlice) {
@@ -270,10 +308,18 @@
return true;
}
}
+ // Special case: default see more just has an action but no other items
+ if (rowSlice.hasHint(HINT_SEE_MORE) && rowItems.isEmpty()) {
+ return true;
+ }
}
return false;
}
+ /**
+ * @return list of {@link SliceItem}s that are valid to display in a row according
+ * to {@link #isValidRowContent(SliceItem, SliceItem)}.
+ */
private static ArrayList<SliceItem> filterInvalidItems(SliceItem rowSlice) {
ArrayList<SliceItem> filteredList = new ArrayList<>();
for (SliceItem i : rowSlice.getSlice().getItems()) {
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/RowView.java b/slices/view/src/main/java/androidx/app/slice/widget/RowView.java
index bcd4f61..8e5bcc6 100644
--- a/slices/view/src/main/java/androidx/app/slice/widget/RowView.java
+++ b/slices/view/src/main/java/androidx/app/slice/widget/RowView.java
@@ -41,8 +41,10 @@
import android.support.annotation.RestrictTo;
import android.util.Log;
import android.util.TypedValue;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -84,6 +86,7 @@
private LinearLayout mEndContainer;
private SeekBar mSeekBar;
private ProgressBar mProgressBar;
+ private View mSeeMoreView;
private int mRowIndex;
private RowContent mRowContent;
@@ -175,6 +178,11 @@
private void populateViews() {
resetView();
+ if (mRowContent.isDefaultSeeMore()) {
+ showSeeMore();
+ return;
+ }
+
boolean showStart = false;
final SliceItem startItem = mRowContent.getStartItem();
if (startItem != null) {
@@ -456,6 +464,26 @@
return addedView != null;
}
+ private void showSeeMore() {
+ Button b = (Button) LayoutInflater.from(getContext()).inflate(
+ R.layout.abc_slice_row_show_more, this, false);
+ b.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ try {
+ mRowContent.getSlice().getAction().send();
+ } catch (CanceledException e) {
+ Log.w(TAG, "PendingIntent for slice cannot be sent", e);
+ }
+ }
+ });
+ if (mTintColor != -1) {
+ b.setTextColor(mTintColor);
+ }
+ mSeeMoreView = b;
+ addView(mSeeMoreView);
+ }
+
@Override
public void onClick(View view) {
if (mRowAction != null && mRowAction.getActionItem() != null && !mRowAction.isToggle()) {
@@ -496,5 +524,8 @@
mDivider.setVisibility(View.GONE);
mSeekBar.setVisibility(View.GONE);
mProgressBar.setVisibility(View.GONE);
+ if (mSeeMoreView != null) {
+ removeView(mSeeMoreView);
+ }
}
}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java b/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java
index 6e1f0c0..35b8a9e 100644
--- a/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java
+++ b/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java
@@ -192,16 +192,17 @@
final int heightAvailable = MeasureSpec.getSize(heightMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int height = heightAvailable;
- if (heightAvailable >= sliceHeight) {
- // Available space is larger than the slice
- if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
- height = sliceHeight;
+ if (heightAvailable >= sliceHeight || heightMode == MeasureSpec.UNSPECIFIED) {
+ // Available space is larger than the slice or we be what we want
+ if (heightMode != MeasureSpec.EXACTLY) {
+ int maxHeight = mIsScrollable ? mMinLargeHeight + actionHeight : sliceHeight;
+ height = Math.min(maxHeight, sliceHeight);
}
} else {
// Not enough space available for slice in current mode
if (getMode() == MODE_LARGE && heightAvailable >= mMinLargeHeight + actionHeight) {
// It's just a slice with scrolling content; cap it to height available.
- height = heightAvailable;
+ height = Math.min(mMinLargeHeight + actionHeight, heightAvailable);
} else if (getMode() == MODE_SHORTCUT) {
// TODO: consider scaling the shortcut to fit if too small
height = mShortcutSize;
diff --git a/slices/view/src/main/res/layout/abc_slice_row_show_more.xml b/slices/view/src/main/res/layout/abc_slice_row_show_more.xml
new file mode 100644
index 0000000..ef65f85
--- /dev/null
+++ b/slices/view/src/main/res/layout/abc_slice_row_show_more.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2018 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.
+ -->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/abc_slice_show_more"/>
\ No newline at end of file
diff --git a/slices/view/src/main/res/values/strings.xml b/slices/view/src/main/res/values/strings.xml
index b72c986..f73c6cc 100644
--- a/slices/view/src/main/res/values/strings.xml
+++ b/slices/view/src/main/res/values/strings.xml
@@ -18,4 +18,6 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Format string for indicating there is more content in a slice view -->
<string name="abc_slice_more_content">+ <xliff:g id="number" example="5">%1$d</xliff:g></string>
+ <!-- String to indicate there is more content to display in a list [CHAR LIMIT=none] -->
+ <string name="abc_slice_show_more">Show more</string>
</resources>
\ No newline at end of file