packages/apps/Settings
修订版 | 228bc78bf9295b51bc1bb6fce46e56aaa1ca49c1 (tree) |
---|---|
时间 | 2020-08-12 22:40:06 |
作者 | Yi-Ling Chuang <emilychuang@goog...> |
Commiter | Yi-Ling Chuang |
[DO NOT MERGE] Pre-allocate height for contextual cards.
To prevent the UI jank causing by the async card loads, we pre-allocate
some space for the card to fill in. After this change, only one card can
be shown at a time.
More details:
- When the card number configuration is set to 0, don't trigger the card
loader.
- The height adjusting logic is as follows.
When Settings is opened, pre-allocate a space first.
After the RV finish laying out, reset the RV to wrap_content. So if the
card has to be expanded(eg. wifi large mode or dismissal view), then it
will adjust the height accordingly. While if a card previously shown
becomes unavailable(dismissed or conditions not meet), we also reset the
RV so the space can be gone.
Bug: 163288869
Test: robotest
Change-Id: I0dcb2dae8f0533e562ad06f664b7ae7a9afecd21
@@ -348,6 +348,7 @@ | ||
348 | 348 | <dimen name="contextual_half_card_padding_top">12dp</dimen> |
349 | 349 | <dimen name="contextual_half_card_padding_bottom">16dp</dimen> |
350 | 350 | <dimen name="contextual_half_card_title_margin_top">12dp</dimen> |
351 | + <dimen name="contextual_card_preallocated_height">0dp</dimen> | |
351 | 352 | |
352 | 353 | <!-- Homepage dismissal cards size and padding --> |
353 | 354 | <dimen name="contextual_card_dismissal_margin_top">12dp</dimen> |
@@ -50,7 +50,7 @@ import java.util.stream.Collectors; | ||
50 | 50 | public class ContextualCardLoader extends AsyncLoaderCompat<List<ContextualCard>> { |
51 | 51 | |
52 | 52 | @VisibleForTesting |
53 | - static final int DEFAULT_CARD_COUNT = 3; | |
53 | + static final int DEFAULT_CARD_COUNT = 1; | |
54 | 54 | @VisibleForTesting |
55 | 55 | static final String CONTEXTUAL_CARD_COUNT = "contextual_card_count"; |
56 | 56 | static final int CARD_CONTENT_LOADER_ID = 1; |
@@ -131,7 +131,7 @@ public class ContextualCardLoader extends AsyncLoaderCompat<List<ContextualCard> | ||
131 | 131 | final List<ContextualCard> visibleCards = new ArrayList<>(); |
132 | 132 | final List<ContextualCard> hiddenCards = new ArrayList<>(); |
133 | 133 | |
134 | - final int maxCardCount = getCardCount(); | |
134 | + final int maxCardCount = getCardCount(mContext); | |
135 | 135 | eligibleCards.forEach(card -> { |
136 | 136 | if (card.getCategory() != STICKY_VALUE) { |
137 | 137 | return; |
@@ -167,11 +167,10 @@ public class ContextualCardLoader extends AsyncLoaderCompat<List<ContextualCard> | ||
167 | 167 | return visibleCards; |
168 | 168 | } |
169 | 169 | |
170 | - @VisibleForTesting | |
171 | - int getCardCount() { | |
170 | + static int getCardCount(Context context) { | |
172 | 171 | // Return the card count if Settings.Global has KEY_CONTEXTUAL_CARD_COUNT key, |
173 | 172 | // otherwise return the default one. |
174 | - return Settings.Global.getInt(mContext.getContentResolver(), | |
173 | + return Settings.Global.getInt(context.getContentResolver(), | |
175 | 174 | CONTEXTUAL_CARD_COUNT, DEFAULT_CARD_COUNT); |
176 | 175 | } |
177 | 176 |
@@ -122,6 +122,10 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo | ||
122 | 122 | Log.w(TAG, "Legacy suggestion contextual card enabled, skipping contextual cards."); |
123 | 123 | return; |
124 | 124 | } |
125 | + if (ContextualCardLoader.getCardCount(mContext) <= 0) { | |
126 | + Log.w(TAG, "Card count is zero, skipping contextual cards."); | |
127 | + return; | |
128 | + } | |
125 | 129 | mStartTime = System.currentTimeMillis(); |
126 | 130 | final CardContentLoaderCallbacks cardContentLoaderCallbacks = |
127 | 131 | new CardContentLoaderCallbacks(mContext); |
@@ -16,7 +16,10 @@ | ||
16 | 16 | |
17 | 17 | package com.android.settings.homepage.contextualcards; |
18 | 18 | |
19 | +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; | |
20 | + | |
19 | 21 | import android.content.Context; |
22 | +import android.util.Log; | |
20 | 23 | import android.view.LayoutInflater; |
21 | 24 | import android.view.View; |
22 | 25 | import android.view.ViewGroup; |
@@ -131,7 +134,22 @@ public class ContextualCardsAdapter extends RecyclerView.Adapter<RecyclerView.Vi | ||
131 | 134 | diffResult.dispatchUpdatesTo(this); |
132 | 135 | } |
133 | 136 | |
134 | - if (mRecyclerView != null && previouslyEmpty && !nowEmpty) { | |
137 | + if (mRecyclerView == null) { | |
138 | + return; | |
139 | + } | |
140 | + | |
141 | + // When no card gets displayed either because a card's condition no longer meets | |
142 | + // or when it's dismissed, the height should be rearranged. | |
143 | + if (mContextualCards.isEmpty()) { | |
144 | + final ViewGroup.LayoutParams params = mRecyclerView.getLayoutParams(); | |
145 | + if (params.height != WRAP_CONTENT) { | |
146 | + Log.d(TAG, "mContextualCards is empty. Set the RV to wrap_content"); | |
147 | + params.height = WRAP_CONTENT; | |
148 | + mRecyclerView.setLayoutParams(params); | |
149 | + } | |
150 | + } | |
151 | + | |
152 | + if (previouslyEmpty && !nowEmpty) { | |
135 | 153 | // Adding items to empty list, should animate. |
136 | 154 | mRecyclerView.scheduleLayoutAnimation(); |
137 | 155 | } |
@@ -16,6 +16,8 @@ | ||
16 | 16 | |
17 | 17 | package com.android.settings.homepage.contextualcards; |
18 | 18 | |
19 | +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; | |
20 | + | |
19 | 21 | import static com.android.settings.homepage.contextualcards.ContextualCardsAdapter.SPAN_COUNT; |
20 | 22 | |
21 | 23 | import android.app.settings.SettingsEnums; |
@@ -34,6 +36,7 @@ import androidx.annotation.VisibleForTesting; | ||
34 | 36 | import androidx.loader.app.LoaderManager; |
35 | 37 | import androidx.recyclerview.widget.GridLayoutManager; |
36 | 38 | import androidx.recyclerview.widget.ItemTouchHelper; |
39 | +import androidx.recyclerview.widget.RecyclerView; | |
37 | 40 | |
38 | 41 | import com.android.settings.R; |
39 | 42 | import com.android.settings.core.InstrumentedFragment; |
@@ -105,8 +108,20 @@ public class ContextualCardsFragment extends InstrumentedFragment implements | ||
105 | 108 | final View rootView = inflater.inflate(R.layout.settings_homepage, container, false); |
106 | 109 | mCardsContainer = rootView.findViewById(R.id.card_container); |
107 | 110 | mLayoutManager = new GridLayoutManager(getActivity(), SPAN_COUNT, |
108 | - GridLayoutManager.VERTICAL, false /* reverseLayout */); | |
111 | + GridLayoutManager.VERTICAL, false /* reverseLayout */) { | |
112 | + @Override | |
113 | + public void onLayoutCompleted(RecyclerView.State state) { | |
114 | + super.onLayoutCompleted(state); | |
115 | + // Once cards finish laying out, make the RV back to wrap content for flexibility. | |
116 | + final ViewGroup.LayoutParams params = mCardsContainer.getLayoutParams(); | |
117 | + if (params.height != WRAP_CONTENT) { | |
118 | + params.height = WRAP_CONTENT; | |
119 | + mCardsContainer.setLayoutParams(params); | |
120 | + } | |
121 | + } | |
122 | + }; | |
109 | 123 | mCardsContainer.setLayoutManager(mLayoutManager); |
124 | + preAllocateHeight(context); | |
110 | 125 | mContextualCardsAdapter = new ContextualCardsAdapter(context, this /* lifecycleOwner */, |
111 | 126 | mContextualCardManager); |
112 | 127 | mCardsContainer.setItemAnimator(null); |
@@ -159,6 +174,25 @@ public class ContextualCardsFragment extends InstrumentedFragment implements | ||
159 | 174 | FeatureFactory.getFactory(context).getSlicesFeatureProvider().newUiSession(); |
160 | 175 | } |
161 | 176 | |
177 | + private void preAllocateHeight(Context context) { | |
178 | + final int cardCount = ContextualCardLoader.getCardCount(context); | |
179 | + if (cardCount != 1) { | |
180 | + // only pre-allocate space when card count is one | |
181 | + Log.d(TAG, "Skip height pre-allocating. card count = " + cardCount); | |
182 | + return; | |
183 | + } | |
184 | + | |
185 | + final int preAllocatedHeight = getResources().getDimensionPixelSize( | |
186 | + R.dimen.contextual_card_preallocated_height); | |
187 | + if (preAllocatedHeight == 0) { | |
188 | + return; | |
189 | + } | |
190 | + | |
191 | + final ViewGroup.LayoutParams params = mCardsContainer.getLayoutParams(); | |
192 | + params.height = preAllocatedHeight; | |
193 | + mCardsContainer.setLayoutParams(params); | |
194 | + } | |
195 | + | |
162 | 196 | /** |
163 | 197 | * Receiver for updating UI session when home key or recent app key is pressed. |
164 | 198 | */ |
@@ -71,25 +71,25 @@ public class ContextualCardLoaderTest { | ||
71 | 71 | } |
72 | 72 | |
73 | 73 | @Test |
74 | - public void getDisplayableCards_twoEligibleCards_shouldShowAll() { | |
74 | + public void getDisplayableCards_twoEligibleCards_notExceedDefaultCardCount() { | |
75 | 75 | final List<ContextualCard> cards = getContextualCardList().stream().limit(2) |
76 | 76 | .collect(Collectors.toList()); |
77 | 77 | doReturn(cards).when(mContextualCardLoader).filterEligibleCards(anyList()); |
78 | 78 | |
79 | 79 | final List<ContextualCard> result = mContextualCardLoader.getDisplayableCards(cards); |
80 | 80 | |
81 | - assertThat(result).hasSize(cards.size()); | |
81 | + assertThat(result).hasSize(Math.min(cards.size(), DEFAULT_CARD_COUNT)); | |
82 | 82 | } |
83 | 83 | |
84 | 84 | @Test |
85 | - public void getDisplayableCards_fourEligibleCards_shouldShowDefaultCardCount() { | |
85 | + public void getDisplayableCards_fourEligibleCards_notExceedDefaultCardCount() { | |
86 | 86 | final List<ContextualCard> cards = getContextualCardList().stream().limit(4) |
87 | 87 | .collect(Collectors.toList()); |
88 | 88 | doReturn(cards).when(mContextualCardLoader).filterEligibleCards(anyList()); |
89 | 89 | |
90 | 90 | final List<ContextualCard> result = mContextualCardLoader.getDisplayableCards(cards); |
91 | 91 | |
92 | - assertThat(result).hasSize(DEFAULT_CARD_COUNT); | |
92 | + assertThat(result).hasSize(Math.min(cards.size(), DEFAULT_CARD_COUNT)); | |
93 | 93 | } |
94 | 94 | |
95 | 95 | @Test |
@@ -139,7 +139,7 @@ public class ContextualCardLoaderTest { | ||
139 | 139 | |
140 | 140 | @Test |
141 | 141 | public void getCardCount_noConfiguredCardCount_returnDefaultCardCount() { |
142 | - assertThat(mContextualCardLoader.getCardCount()).isEqualTo(DEFAULT_CARD_COUNT); | |
142 | + assertThat(mContextualCardLoader.getCardCount(mContext)).isEqualTo(DEFAULT_CARD_COUNT); | |
143 | 143 | } |
144 | 144 | |
145 | 145 | @Test |
@@ -148,7 +148,7 @@ public class ContextualCardLoaderTest { | ||
148 | 148 | Settings.Global.putLong(mContext.getContentResolver(), |
149 | 149 | ContextualCardLoader.CONTEXTUAL_CARD_COUNT, configCount); |
150 | 150 | |
151 | - assertThat(mContextualCardLoader.getCardCount()).isEqualTo(configCount); | |
151 | + assertThat(mContextualCardLoader.getCardCount(mContext)).isEqualTo(configCount); | |
152 | 152 | } |
153 | 153 | |
154 | 154 | private List<ContextualCard> getContextualCardList() { |