1 /* 2 * Copyright 2017 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 androidx.slice.widget; 18 19 import static androidx.slice.widget.SliceView.MODE_SMALL; 20 21 import android.content.Context; 22 import android.os.Build; 23 import android.util.AttributeSet; 24 import android.view.MotionEvent; 25 import android.view.View; 26 import android.widget.FrameLayout; 27 28 import androidx.annotation.RestrictTo; 29 import androidx.recyclerview.widget.LinearLayoutManager; 30 import androidx.recyclerview.widget.RecyclerView; 31 import androidx.slice.SliceItem; 32 33 import java.util.ArrayList; 34 import java.util.Collections; 35 import java.util.List; 36 37 /** 38 * @hide 39 */ 40 @RestrictTo(RestrictTo.Scope.LIBRARY) 41 public class LargeTemplateView extends SliceChildView { 42 43 private SliceView mParent; 44 private final View mForeground; 45 private final LargeSliceAdapter mAdapter; 46 private final RecyclerView mRecyclerView; 47 private boolean mIsScrollable; 48 private ListContent mListContent; 49 private List<SliceItem> mDisplayedItems = new ArrayList<>(); 50 private int mDisplayedItemsHeight = 0; 51 private int[] mLoc = new int[2]; 52 53 public LargeTemplateView(Context context) { 54 super(context); 55 mRecyclerView = new RecyclerView(getContext()); 56 mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 57 mAdapter = new LargeSliceAdapter(context); 58 mRecyclerView.setAdapter(mAdapter); 59 addView(mRecyclerView); 60 61 mForeground = new View(getContext()); 62 mForeground.setBackground(SliceViewUtil.getDrawable(getContext(), 63 android.R.attr.selectableItemBackground)); 64 addView(mForeground); 65 66 FrameLayout.LayoutParams lp = (LayoutParams) mForeground.getLayoutParams(); 67 lp.width = LayoutParams.MATCH_PARENT; 68 lp.height = LayoutParams.MATCH_PARENT; 69 mForeground.setLayoutParams(lp); 70 } 71 72 @Override 73 public void onAttachedToWindow() { 74 super.onAttachedToWindow(); 75 mParent = (SliceView) getParent(); 76 mAdapter.setParents(mParent, this); 77 } 78 79 @Override 80 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 81 int height = MeasureSpec.getSize(heightMeasureSpec); 82 if (mDisplayedItems.size() > 0 && mDisplayedItemsHeight > height) { 83 // Need to resize 84 updateDisplayedItems(height); 85 } 86 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 87 } 88 89 /** 90 * Called when the foreground view handling touch feedback should be activated. 91 * @param event the event to handle. 92 */ 93 public void onForegroundActivated(MotionEvent event) { 94 if (mParent != null && !mParent.isSliceViewClickable()) { 95 // Only show highlight if clickable 96 mForeground.setPressed(false); 97 return; 98 } 99 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 100 mForeground.getLocationOnScreen(mLoc); 101 final int x = (int) (event.getRawX() - mLoc[0]); 102 final int y = (int) (event.getRawY() - mLoc[1]); 103 mForeground.getBackground().setHotspot(x, y); 104 } 105 int action = event.getActionMasked(); 106 if (action == MotionEvent.ACTION_DOWN) { 107 mForeground.setPressed(true); 108 } else if (action == MotionEvent.ACTION_CANCEL 109 || action == MotionEvent.ACTION_UP 110 || action == MotionEvent.ACTION_MOVE) { 111 mForeground.setPressed(false); 112 } 113 } 114 115 @Override 116 public void setMode(int newMode) { 117 super.setMode(newMode); 118 updateDisplayedItems(getMeasuredHeight()); 119 } 120 121 @Override 122 public int getActualHeight() { 123 return mDisplayedItemsHeight; 124 } 125 126 @Override 127 public int getSmallHeight() { 128 if (mListContent == null || mListContent.getHeaderItem() == null) { 129 return 0; 130 } 131 SliceItem headerItem = mListContent.getHeaderItem(); 132 return mListContent.getHeight(getContext(), headerItem, true /* isHeader */, 133 0 /* rowIndex */, 1 /* rowCount */, MODE_SMALL); 134 } 135 136 @Override 137 public void setTint(int tint) { 138 super.setTint(tint); 139 populate(); 140 } 141 142 @Override 143 public void setSliceActionListener(SliceView.OnSliceActionListener observer) { 144 mObserver = observer; 145 if (mAdapter != null) { 146 mAdapter.setSliceObserver(mObserver); 147 } 148 } 149 150 @Override 151 public void setSliceActions(List<SliceItem> actions) { 152 mAdapter.setSliceActions(actions); 153 } 154 155 @Override 156 public void setSliceContent(ListContent sliceContent) { 157 mListContent = sliceContent; 158 populate(); 159 } 160 161 @Override 162 public void setStyle(AttributeSet attrs, int defStyleAttrs, int defStyleRes) { 163 super.setStyle(attrs, defStyleAttrs, defStyleRes); 164 mAdapter.setStyle(attrs, defStyleAttrs, defStyleRes); 165 } 166 167 @Override 168 public void setShowLastUpdated(boolean showLastUpdated) { 169 super.setShowLastUpdated(showLastUpdated); 170 mAdapter.setShowLastUpdated(showLastUpdated); 171 } 172 173 @Override 174 public void setLastUpdated(long lastUpdated) { 175 super.setLastUpdated(lastUpdated); 176 mAdapter.setLastUpdated(lastUpdated); 177 } 178 179 private void populate() { 180 if (mListContent == null) { 181 resetView(); 182 return; 183 } 184 updateDisplayedItems(getMeasuredHeight()); 185 } 186 187 /** 188 * Whether or not the content in this template should be scrollable. 189 */ 190 public void setScrollable(boolean isScrollable) { 191 mIsScrollable = isScrollable; 192 updateDisplayedItems(getMeasuredHeight()); 193 } 194 195 private void updateDisplayedItems(int height) { 196 if (mListContent == null) { 197 return; 198 } 199 if (!mIsScrollable) { 200 // If we're not scrollable we must cap the number of items we're displaying such 201 // that they fit in the available space 202 if (height == 0) { 203 // Not measured, use default 204 mDisplayedItems = mListContent.getItemsForNonScrollingList(-1); 205 } else { 206 mDisplayedItems = mListContent.getItemsForNonScrollingList(height); 207 } 208 } else { 209 mDisplayedItems = mListContent.getRowItems(); 210 } 211 mDisplayedItemsHeight = mListContent.getListHeight(getContext(), mDisplayedItems); 212 int mode = getMode(); 213 if (mode == SliceView.MODE_LARGE) { 214 mAdapter.setSliceItems(mDisplayedItems, mTintColor, mode); 215 } else if (mode == MODE_SMALL) { 216 mAdapter.setSliceItems( 217 Collections.singletonList(mDisplayedItems.get(0)), mTintColor, mode); 218 } 219 } 220 221 @Override 222 public void resetView() { 223 mDisplayedItemsHeight = 0; 224 mDisplayedItems.clear(); 225 mAdapter.setSliceItems(null, -1, getMode()); 226 mListContent = null; 227 } 228 } 229