1 /* 2 * Copyright (C) 2023 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.google.android.car.kitchensink; 18 19 import android.content.Context; 20 import android.graphics.drawable.Drawable; 21 import android.text.TextUtils; 22 import android.util.TypedValue; 23 import android.view.View; 24 25 import androidx.annotation.DrawableRes; 26 import androidx.annotation.NonNull; 27 import androidx.recyclerview.widget.RecyclerView; 28 29 import com.android.car.ui.recyclerview.CarUiListItemAdapter; 30 import com.android.car.ui.recyclerview.CarUiRecyclerView; 31 import com.android.car.ui.widget.CarUiTextView; 32 33 import java.util.List; 34 35 /** RecyclerView adapter that supports single-preference highlighting. */ 36 public class HighlightableAdapter extends CarUiListItemAdapter { 37 38 private static final int INVALID_BACKGROUND_RES = -1; 39 private static final String EMPTY_STRING = ""; 40 @DrawableRes 41 private final int mDefaultBackgroundRes; 42 @DrawableRes 43 private final int mHighlightBackgroundRes; 44 private final int mItemHeight; 45 private final Drawable mPinIconDrawable; 46 private final int mPinIconPadding; 47 private String mHighlightTitle = EMPTY_STRING; 48 private final List<FragmentListItem> mListItems; 49 private final CarUiRecyclerView mRecyclerView; 50 private int mHighlightPosition; 51 HighlightableAdapter(Context context, List<FragmentListItem> data, CarUiRecyclerView recyclerView)52 public HighlightableAdapter(Context context, List<FragmentListItem> data, 53 CarUiRecyclerView recyclerView) { 54 this(context, data, recyclerView, INVALID_BACKGROUND_RES, 55 R.drawable.preference_highlight_default); 56 } 57 HighlightableAdapter(Context context, List<FragmentListItem> data, CarUiRecyclerView recyclerView, @DrawableRes int defaultBackgroundRes, @DrawableRes int highlightBackgroundRes)58 public HighlightableAdapter(Context context, List<FragmentListItem> data, 59 CarUiRecyclerView recyclerView, 60 @DrawableRes int defaultBackgroundRes, @DrawableRes int highlightBackgroundRes) { 61 super(data); 62 mListItems = data; 63 mRecyclerView = recyclerView; 64 if (defaultBackgroundRes == INVALID_BACKGROUND_RES) { 65 TypedValue outValue = new TypedValue(); 66 context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, 67 outValue, /* resolveRefs= */ true); 68 mDefaultBackgroundRes = outValue.resourceId; 69 } else { 70 mDefaultBackgroundRes = defaultBackgroundRes; 71 } 72 mHighlightBackgroundRes = highlightBackgroundRes; 73 74 mItemHeight = context.getResources().getDimensionPixelSize( 75 R.dimen.top_level_preference_height); 76 mPinIconDrawable = context.getDrawable(R.drawable.ic_item_pin); 77 mPinIconPadding = context.getResources().getDimensionPixelSize( 78 R.dimen.top_level_pin_icon_padding); 79 } 80 81 @Override onBindViewHolder(@onNull RecyclerView.ViewHolder holder, int position)82 public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 83 super.onBindViewHolder(holder, position); 84 holder.itemView.setSelected(mHighlightPosition == position); 85 holder.itemView.getLayoutParams().height = mItemHeight; 86 87 //itemView is inflated by CarUiListItemAdapter 88 CarUiTextView titleView = holder.itemView.findViewById(R.id.car_ui_list_item_title); 89 if (titleView != null) { 90 titleView.setEllipsize(TextUtils.TruncateAt.END); 91 titleView.setSingleLine(true); 92 if (mListItems.get(position).isFavourite()) { 93 titleView.setCompoundDrawablesWithIntrinsicBounds(null, null, mPinIconDrawable, 94 null); 95 titleView.setPaddingRelative(0, 0, mPinIconPadding, 0); 96 } else { 97 titleView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); 98 } 99 } 100 updateBackground(holder, position); 101 } 102 updateBackground(RecyclerView.ViewHolder holder, int position)103 private void updateBackground(RecyclerView.ViewHolder holder, int position) { 104 View v = holder.itemView; 105 if (mHighlightPosition == position) { 106 addHighlightBackground(v); 107 } else if (hasHighlightBackground(v)) { 108 removeHighlightBackground(v); 109 } 110 } 111 112 /** 113 * Requests that a particular list item be highlighted. This will remove the highlight from 114 * the previously highlighted item. 115 */ requestHighlight(String fragmentTitle, int newPosition)116 public void requestHighlight(String fragmentTitle, int newPosition) { 117 if (mRecyclerView == null) { 118 return; 119 } 120 121 if (mHighlightPosition < 0) { 122 // Item is not in the list - clearing the previous highlight without setting a new one. 123 mHighlightTitle = EMPTY_STRING; 124 mRecyclerView.getView().post(() -> notifyItemChanged(mHighlightPosition)); 125 } 126 127 if (newPosition >= 0) { 128 mHighlightTitle = fragmentTitle; 129 mRecyclerView.getView().post(() -> { 130 notifyItemChanged(mHighlightPosition); 131 mHighlightPosition = newPosition; 132 mRecyclerView.scrollToPosition(newPosition); 133 notifyItemChanged(newPosition); 134 }); 135 } 136 } 137 getPositionFromTitle(String fragmentTitle)138 private int getPositionFromTitle(String fragmentTitle) { 139 for (int i = 0; i < mListItems.size(); i++) { 140 String targetText = mListItems.get(i).getTitle().getPreferredText().toString(); 141 if (targetText.equalsIgnoreCase(fragmentTitle)) { 142 return i; 143 } 144 } 145 return RecyclerView.NO_POSITION; 146 } 147 addHighlightBackground(View v)148 private void addHighlightBackground(View v) { 149 v.setTag(R.id.preference_highlighted, true); 150 v.setBackgroundResource(mHighlightBackgroundRes); 151 } 152 removeHighlightBackground(View v)153 private void removeHighlightBackground(View v) { 154 v.setTag(R.id.preference_highlighted, false); 155 v.setBackgroundResource(mDefaultBackgroundRes); 156 } 157 hasHighlightBackground(View v)158 private boolean hasHighlightBackground(View v) { 159 return Boolean.TRUE.equals(v.getTag(R.id.preference_highlighted)); 160 } 161 162 /** 163 * Will be called each time the search query is changed. 164 */ afterTextChanged()165 public void afterTextChanged() { 166 mHighlightPosition = getPositionFromTitle(mHighlightTitle); 167 notifyDataSetChanged(); 168 } 169 170 /** 171 * Scrolls to the highlighted item after search ends. 172 */ onSearchEnded()173 public void onSearchEnded() { 174 mRecyclerView.getView().post(() -> mRecyclerView.scrollToPosition(mHighlightPosition)); 175 } 176 afterFavClicked(int from, int to)177 public void afterFavClicked(int from, int to) { 178 mHighlightPosition = to; 179 notifyItemMoved(from, to); 180 notifyItemChanged(to); 181 mRecyclerView.scrollToPosition(to); 182 } 183 } 184