1 /* 2 * Copyright (C) 2015 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.setupdesign.items; 18 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.graphics.Color; 22 import android.graphics.drawable.Drawable; 23 import android.util.AttributeSet; 24 import android.view.Gravity; 25 import android.view.View; 26 import android.view.ViewGroup.LayoutParams; 27 import android.widget.ImageView; 28 import android.widget.LinearLayout; 29 import android.widget.TextView; 30 import androidx.annotation.ColorInt; 31 import androidx.annotation.Nullable; 32 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; 33 import com.google.android.setupdesign.R; 34 import com.google.android.setupdesign.span.LinkSpan; 35 import com.google.android.setupdesign.util.ItemStyler; 36 import com.google.android.setupdesign.util.LayoutStyler; 37 import com.google.android.setupdesign.view.RichTextView; 38 39 /** 40 * Definition of an item in an {@link ItemHierarchy}. An item is usually defined in XML and inflated 41 * using {@link ItemInflater}. 42 */ 43 public class Item extends AbstractItem implements LinkSpan.OnLinkClickListener { 44 45 /** 46 * Listener that is invoked when a link span is clicked in summary RichTextView. 47 * If the containing view of this span implements this interface, this will be invoked when the 48 * link is clicked. 49 * @apiNote Make sure to use RichTextView for the textViews wherever Linking of text is expected. 50 * This OnLinkClickListener can be extended to Title TextViews based on use case. 51 */ 52 public interface OnItemTextLinkClickListener { 53 54 /** 55 * Called when a link has been clicked. 56 * 57 * @param span The span that was clicked. 58 * @return True if the click was handled, stopping further propagation of the click event. 59 */ onItemTextLinkClicked(LinkSpan span)60 boolean onItemTextLinkClicked(LinkSpan span); 61 } 62 63 private boolean enabled = true; 64 @Nullable private Drawable icon; 65 private int layoutRes; 66 @Nullable private CharSequence summary; 67 @Nullable private CharSequence title; 68 @Nullable private CharSequence contentDescription; 69 @Nullable private Boolean isClickable; 70 @Nullable private OnItemTextLinkClickListener itemTextLinkClickListener; 71 private boolean visible = true; 72 @ColorInt private int iconTint = Color.TRANSPARENT; 73 private int iconGravity = Gravity.CENTER_VERTICAL; 74 Item()75 public Item() { 76 super(); 77 layoutRes = getDefaultLayoutResource(); 78 } 79 Item(Context context, AttributeSet attrs)80 public Item(Context context, AttributeSet attrs) { 81 super(context, attrs); 82 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SudItem); 83 enabled = a.getBoolean(R.styleable.SudItem_android_enabled, true); 84 icon = a.getDrawable(R.styleable.SudItem_android_icon); 85 title = a.getText(R.styleable.SudItem_android_title); 86 summary = a.getText(R.styleable.SudItem_android_summary); 87 contentDescription = a.getText(R.styleable.SudItem_android_contentDescription); 88 layoutRes = a.getResourceId(R.styleable.SudItem_android_layout, getDefaultLayoutResource()); 89 visible = a.getBoolean(R.styleable.SudItem_android_visible, true); 90 iconTint = a.getColor(R.styleable.SudItem_sudIconTint, Color.TRANSPARENT); 91 iconGravity = a.getInt(R.styleable.SudItem_sudIconGravity, Gravity.CENTER_VERTICAL); 92 93 a.recycle(); 94 } 95 getDefaultLayoutResource()96 protected int getDefaultLayoutResource() { 97 return R.layout.sud_items_default; 98 } 99 setEnabled(boolean enabled)100 public void setEnabled(boolean enabled) { 101 if (this.enabled == enabled) { 102 return; 103 } 104 this.enabled = enabled; 105 notifyItemChanged(); 106 } 107 108 @Override getCount()109 public int getCount() { 110 return isVisible() ? 1 : 0; 111 } 112 113 @Override isEnabled()114 public boolean isEnabled() { 115 return enabled; 116 } 117 setIcon(@ullable Drawable icon)118 public void setIcon(@Nullable Drawable icon) { 119 this.icon = icon; 120 notifyItemChanged(); 121 } 122 123 @Nullable getIcon()124 public Drawable getIcon() { 125 return icon; 126 } 127 setIconTint(@olorInt int iconTint)128 public void setIconTint(@ColorInt int iconTint) { 129 this.iconTint = iconTint; 130 } 131 132 @ColorInt getIconTint()133 public int getIconTint() { 134 return iconTint; 135 } 136 setIconGravity(int iconGravity)137 public void setIconGravity(int iconGravity) { 138 this.iconGravity = iconGravity; 139 } 140 getClickable()141 public Boolean getClickable() { 142 return isClickable; 143 } 144 setClickable(Boolean isClickable)145 public void setClickable(Boolean isClickable) { 146 this.isClickable = isClickable; 147 } 148 getIconGravity()149 public int getIconGravity() { 150 return iconGravity; 151 } 152 setLayoutResource(int layoutResource)153 public void setLayoutResource(int layoutResource) { 154 layoutRes = layoutResource; 155 notifyItemChanged(); 156 } 157 158 @Override getLayoutResource()159 public int getLayoutResource() { 160 return layoutRes; 161 } 162 setSummary(@ullable CharSequence summary)163 public void setSummary(@Nullable CharSequence summary) { 164 this.summary = summary; 165 notifyItemChanged(); 166 } 167 168 @Nullable getSummary()169 public CharSequence getSummary() { 170 return summary; 171 } 172 setOnItemTextLinkClickListener(@ullable OnItemTextLinkClickListener itemTextLinkClickListener)173 public void setOnItemTextLinkClickListener(@Nullable OnItemTextLinkClickListener itemTextLinkClickListener) { 174 this.itemTextLinkClickListener = itemTextLinkClickListener; 175 } 176 setTitle(@ullable CharSequence title)177 public void setTitle(@Nullable CharSequence title) { 178 this.title = title; 179 notifyItemChanged(); 180 } 181 182 @Nullable getTitle()183 public CharSequence getTitle() { 184 return title; 185 } 186 187 @Nullable getContentDescription()188 public CharSequence getContentDescription() { 189 return contentDescription; 190 } 191 setContentDescription(@ullable CharSequence contentDescription)192 public void setContentDescription(@Nullable CharSequence contentDescription) { 193 this.contentDescription = contentDescription; 194 notifyItemChanged(); 195 } 196 setVisible(boolean visible)197 public void setVisible(boolean visible) { 198 if (this.visible == visible) { 199 return; 200 } 201 this.visible = visible; 202 if (!visible) { 203 notifyItemRangeRemoved(0, 1); 204 } else { 205 notifyItemRangeInserted(0, 1); 206 } 207 } 208 isVisible()209 public boolean isVisible() { 210 return visible; 211 } 212 hasSummary(CharSequence summary)213 private boolean hasSummary(CharSequence summary) { 214 return summary != null && summary.length() > 0; 215 } 216 217 @Override getViewId()218 public int getViewId() { 219 return getId(); 220 } 221 222 @Override onBindView(View view)223 public void onBindView(View view) { 224 TextView label = (TextView) view.findViewById(R.id.sud_items_title); 225 label.setText(getTitle()); 226 if (isClickable != null) { 227 view.setClickable(isClickable); 228 } 229 TextView summaryView = (TextView) view.findViewById(R.id.sud_items_summary); 230 CharSequence summary = getSummary(); 231 if (hasSummary(summary)) { 232 summaryView.setText(summary); 233 if (summaryView instanceof RichTextView tv) { 234 tv.setOnLinkClickListener(this); 235 } 236 summaryView.setVisibility(View.VISIBLE); 237 } else { 238 summaryView.setVisibility(View.GONE); 239 } 240 241 view.setContentDescription(getContentDescription()); 242 243 final View iconContainer = view.findViewById(R.id.sud_items_icon_container); 244 final Drawable icon = getIcon(); 245 if (icon != null) { 246 final ImageView iconView = (ImageView) view.findViewById(R.id.sud_items_icon); 247 // Set the image drawable to null before setting the state and level to avoid affecting 248 // any recycled drawable in the ImageView 249 iconView.setImageDrawable(null); 250 onMergeIconStateAndLevels(iconView, icon); 251 iconView.setImageDrawable(icon); 252 if (iconTint != Color.TRANSPARENT) { 253 iconView.setColorFilter(iconTint); 254 } else { 255 iconView.clearColorFilter(); 256 } 257 LayoutParams layoutParams = iconContainer.getLayoutParams(); 258 if (layoutParams instanceof LinearLayout.LayoutParams) { 259 ((LinearLayout.LayoutParams) layoutParams).gravity = iconGravity; 260 } 261 iconContainer.setVisibility(View.VISIBLE); 262 } else { 263 iconContainer.setVisibility(View.GONE); 264 } 265 266 view.setId(getViewId()); 267 268 // ExpandableSwitchItem uses its child view to apply the style SudItemContainer. It is not 269 // possible to directly adjust the padding start/end of the item's layout here. It needs to 270 // get its child view to adjust it first, so skip the Layout padding adjustment. 271 // If the item view is a header layout, it doesn't need to adjust the layout padding start/end 272 // here. It will be adjusted by HeaderMixin. 273 // TODO: Add partner resource enable check 274 if (!(this instanceof ExpandableSwitchItem) && view.getId() != R.id.sud_layout_header) { 275 if (!PartnerConfigHelper.isGlifExpressiveEnabled(view.getContext())) { 276 LayoutStyler.applyPartnerCustomizationLayoutPaddingStyle(view); 277 } 278 } 279 ItemStyler.applyPartnerCustomizationItemStyle(view); 280 } 281 282 /** 283 * Copies state and level information from {@link #getIcon()} to the currently bound view's 284 * ImageView. Subclasses can override this method to change whats being copied from the icon to 285 * the ImageView. 286 */ onMergeIconStateAndLevels(ImageView iconView, Drawable icon)287 protected void onMergeIconStateAndLevels(ImageView iconView, Drawable icon) { 288 iconView.setImageState(icon.getState(), false /* merge */); 289 iconView.setImageLevel(icon.getLevel()); 290 } 291 292 @Override onLinkClick(LinkSpan span)293 public boolean onLinkClick(LinkSpan span) { 294 if (itemTextLinkClickListener != null) { 295 return itemTextLinkClickListener.onItemTextLinkClicked(span); 296 } 297 return false; 298 } 299 300 } 301