1 /* 2 * Copyright (C) 2022 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 com.android.intentresolver.grid; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.animation.ValueAnimator; 23 import android.view.View; 24 import android.view.View.MeasureSpec; 25 import android.view.ViewGroup; 26 import android.view.animation.AccelerateInterpolator; 27 28 import androidx.recyclerview.widget.RecyclerView; 29 30 import com.android.intentresolver.ChooserActivity; 31 32 import java.util.Arrays; 33 import java.util.List; 34 import java.util.function.Supplier; 35 36 /** Holder for direct share targets in the {@link ChooserGridAdapter}. */ 37 public class DirectShareViewHolder extends ItemGroupViewHolder { 38 private final ViewGroup mParent; 39 private final List<ViewGroup> mRows; 40 private int mCellCountPerRow; 41 42 private boolean mHideDirectShareExpansion = false; 43 private int mDirectShareMinHeight = 0; 44 private int mDirectShareCurrHeight = 0; 45 private int mDirectShareMaxHeight = 0; 46 47 private final boolean[] mCellVisibility; 48 49 private final Supplier<Integer> mDeferredTargetCountSupplier; 50 DirectShareViewHolder( ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow, int viewType, Supplier<Integer> deferredTargetCountSupplier)51 public DirectShareViewHolder( 52 ViewGroup parent, 53 List<ViewGroup> rows, 54 int cellCountPerRow, 55 int viewType, 56 Supplier<Integer> deferredTargetCountSupplier) { 57 super(rows.size() * cellCountPerRow, parent, viewType); 58 59 this.mParent = parent; 60 this.mRows = rows; 61 this.mCellCountPerRow = cellCountPerRow; 62 this.mCellVisibility = new boolean[rows.size() * cellCountPerRow]; 63 Arrays.fill(mCellVisibility, true); 64 this.mDeferredTargetCountSupplier = deferredTargetCountSupplier; 65 } 66 addView(int index, View v)67 public ViewGroup addView(int index, View v) { 68 ViewGroup row = getRowByIndex(index); 69 row.addView(v); 70 mCells[index] = v; 71 72 return row; 73 } 74 getViewGroup()75 public ViewGroup getViewGroup() { 76 return mParent; 77 } 78 getRowByIndex(int index)79 public ViewGroup getRowByIndex(int index) { 80 return mRows.get(index / mCellCountPerRow); 81 } 82 getRow(int rowNumber)83 public ViewGroup getRow(int rowNumber) { 84 return mRows.get(rowNumber); 85 } 86 measure()87 public void measure() { 88 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 89 getRow(0).measure(spec, spec); 90 getRow(1).measure(spec, spec); 91 92 mDirectShareMinHeight = getRow(0).getMeasuredHeight(); 93 mDirectShareCurrHeight = (mDirectShareCurrHeight > 0) 94 ? mDirectShareCurrHeight : mDirectShareMinHeight; 95 mDirectShareMaxHeight = 2 * mDirectShareMinHeight; 96 } 97 getMeasuredRowHeight()98 public int getMeasuredRowHeight() { 99 return mDirectShareCurrHeight; 100 } 101 getMinRowHeight()102 public int getMinRowHeight() { 103 return mDirectShareMinHeight; 104 } 105 setViewVisibility(int i, int visibility)106 public void setViewVisibility(int i, int visibility) { 107 final View v = getView(i); 108 if (visibility == View.VISIBLE) { 109 mCellVisibility[i] = true; 110 v.setVisibility(visibility); 111 v.setAlpha(1.0f); 112 } else if (visibility == View.INVISIBLE && mCellVisibility[i]) { 113 mCellVisibility[i] = false; 114 115 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f); 116 fadeAnim.setDuration(ChooserGridAdapter.NO_DIRECT_SHARE_ANIM_IN_MILLIS); 117 fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f)); 118 fadeAnim.addListener(new AnimatorListenerAdapter() { 119 public void onAnimationEnd(Animator animation) { 120 v.setVisibility(View.INVISIBLE); 121 } 122 }); 123 fadeAnim.start(); 124 } 125 } 126 handleScroll(RecyclerView view, int y, int oldy, int maxTargetsPerRow)127 public void handleScroll(RecyclerView view, int y, int oldy, int maxTargetsPerRow) { 128 // only exit early if fully collapsed, otherwise onListRebuilt() with shifting 129 // targets can lock us into an expanded mode 130 boolean notExpanded = mDirectShareCurrHeight == mDirectShareMinHeight; 131 if (notExpanded) { 132 if (mHideDirectShareExpansion) { 133 return; 134 } 135 136 // only expand if we have more than maxTargetsPerRow, and delay that decision 137 // until they start to scroll 138 final int validTargets = this.mDeferredTargetCountSupplier.get(); 139 if (validTargets <= maxTargetsPerRow) { 140 mHideDirectShareExpansion = true; 141 return; 142 } 143 } 144 145 int yDiff = (int) ((oldy - y) * ChooserActivity.DIRECT_SHARE_EXPANSION_RATE); 146 147 int prevHeight = mDirectShareCurrHeight; 148 int newHeight = Math.min(prevHeight + yDiff, mDirectShareMaxHeight); 149 newHeight = Math.max(newHeight, mDirectShareMinHeight); 150 yDiff = newHeight - prevHeight; 151 152 updateDirectShareRowHeight(view, yDiff, newHeight); 153 } 154 expand(RecyclerView view)155 public void expand(RecyclerView view) { 156 updateDirectShareRowHeight( 157 view, mDirectShareMaxHeight - mDirectShareCurrHeight, mDirectShareMaxHeight); 158 } 159 collapse(RecyclerView view)160 public void collapse(RecyclerView view) { 161 updateDirectShareRowHeight( 162 view, mDirectShareMinHeight - mDirectShareCurrHeight, mDirectShareMinHeight); 163 } 164 updateDirectShareRowHeight(RecyclerView view, int yDiff, int newHeight)165 private void updateDirectShareRowHeight(RecyclerView view, int yDiff, int newHeight) { 166 if (view == null || view.getChildCount() == 0 || yDiff == 0) { 167 return; 168 } 169 170 // locate the item to expand, and offset the rows below that one 171 boolean foundExpansion = false; 172 for (int i = 0; i < view.getChildCount(); i++) { 173 View child = view.getChildAt(i); 174 175 if (foundExpansion) { 176 child.offsetTopAndBottom(yDiff); 177 } else { 178 if (child.getTag() != null && child.getTag() instanceof DirectShareViewHolder) { 179 int widthSpec = MeasureSpec.makeMeasureSpec(child.getWidth(), 180 MeasureSpec.EXACTLY); 181 int heightSpec = MeasureSpec.makeMeasureSpec(newHeight, 182 MeasureSpec.EXACTLY); 183 child.measure(widthSpec, heightSpec); 184 child.getLayoutParams().height = child.getMeasuredHeight(); 185 child.layout(child.getLeft(), child.getTop(), child.getRight(), 186 child.getTop() + child.getMeasuredHeight()); 187 188 foundExpansion = true; 189 } 190 } 191 } 192 193 if (foundExpansion) { 194 mDirectShareCurrHeight = newHeight; 195 } 196 } 197 } 198