1 /* 2 * Copyright (C) 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 com.google.android.setupdesign.items; 18 19 import android.content.Context; 20 import android.content.res.ColorStateList; 21 import android.content.res.TypedArray; 22 import android.graphics.PorterDuff.Mode; 23 import android.graphics.drawable.Drawable; 24 import android.os.Build.VERSION; 25 import android.os.Build.VERSION_CODES; 26 import android.os.Bundle; 27 import android.util.AttributeSet; 28 import android.view.Gravity; 29 import android.view.View; 30 import android.view.View.OnClickListener; 31 import android.widget.CompoundButton.OnCheckedChangeListener; 32 import android.widget.TextView; 33 import androidx.core.view.AccessibilityDelegateCompat; 34 import androidx.core.view.ViewCompat; 35 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 36 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; 37 import com.google.android.setupdesign.R; 38 import com.google.android.setupdesign.util.LayoutStyler; 39 import com.google.android.setupdesign.view.CheckableLinearLayout; 40 41 /** 42 * A switch item which is divided into two parts: the start (left for LTR) side shows the title and 43 * summary, and when that is clicked, will expand to show a longer summary. The end (right for LTR) 44 * side is a switch which can be toggled by the user. 45 * 46 * <p>Note: It is highly recommended to use this item with recycler view rather than list view, 47 * because list view draws the touch ripple effect on top of the item, rather than letting the item 48 * handle it. Therefore you might see a double-ripple, one for the expandable area and one for the 49 * entire list item, when using this in list view. 50 */ 51 public class ExpandableSwitchItem extends SwitchItem 52 implements OnCheckedChangeListener, OnClickListener { 53 54 private CharSequence collapsedSummary; 55 private CharSequence expandedSummary; 56 private boolean isExpanded = false; 57 58 private final AccessibilityDelegateCompat accessibilityDelegate = 59 new AccessibilityDelegateCompat() { 60 @Override 61 public void onInitializeAccessibilityNodeInfo( 62 View view, AccessibilityNodeInfoCompat nodeInfo) { 63 super.onInitializeAccessibilityNodeInfo(view, nodeInfo); 64 nodeInfo.addAction( 65 isExpanded() 66 ? AccessibilityActionCompat.ACTION_COLLAPSE 67 : AccessibilityActionCompat.ACTION_EXPAND); 68 } 69 70 @Override 71 public boolean performAccessibilityAction(View view, int action, Bundle args) { 72 boolean result; 73 switch (action) { 74 case AccessibilityNodeInfoCompat.ACTION_COLLAPSE: 75 case AccessibilityNodeInfoCompat.ACTION_EXPAND: 76 setExpanded(!isExpanded()); 77 result = true; 78 break; 79 default: 80 result = super.performAccessibilityAction(view, action, args); 81 break; 82 } 83 return result; 84 } 85 }; 86 ExpandableSwitchItem()87 public ExpandableSwitchItem() { 88 super(); 89 setIconGravity(Gravity.TOP); 90 } 91 ExpandableSwitchItem(Context context, AttributeSet attrs)92 public ExpandableSwitchItem(Context context, AttributeSet attrs) { 93 super(context, attrs); 94 final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SudExpandableSwitchItem); 95 collapsedSummary = a.getText(R.styleable.SudExpandableSwitchItem_sudCollapsedSummary); 96 expandedSummary = a.getText(R.styleable.SudExpandableSwitchItem_sudExpandedSummary); 97 setIconGravity(a.getInt(R.styleable.SudItem_sudIconGravity, Gravity.TOP)); 98 a.recycle(); 99 } 100 101 @Override getDefaultLayoutResource()102 protected int getDefaultLayoutResource() { 103 return R.layout.sud_items_expandable_switch; 104 } 105 106 @Override getSummary()107 public CharSequence getSummary() { 108 return isExpanded ? getExpandedSummary() : getCollapsedSummary(); 109 } 110 111 /** @return True if the item is currently expanded. */ isExpanded()112 public boolean isExpanded() { 113 return isExpanded; 114 } 115 116 /** Sets whether the item should be expanded. */ setExpanded(boolean expanded)117 public void setExpanded(boolean expanded) { 118 if (isExpanded == expanded) { 119 return; 120 } 121 isExpanded = expanded; 122 notifyItemChanged(); 123 } 124 125 /** @return The summary shown when in collapsed state. */ getCollapsedSummary()126 public CharSequence getCollapsedSummary() { 127 return collapsedSummary; 128 } 129 130 /** 131 * Sets the summary text shown when the item is collapsed. Corresponds to the {@code 132 * app:sudCollapsedSummary} XML attribute. 133 */ setCollapsedSummary(CharSequence collapsedSummary)134 public void setCollapsedSummary(CharSequence collapsedSummary) { 135 this.collapsedSummary = collapsedSummary; 136 if (!isExpanded()) { 137 notifyChanged(); 138 } 139 } 140 141 /** @return The summary shown when in expanded state. */ getExpandedSummary()142 public CharSequence getExpandedSummary() { 143 return expandedSummary; 144 } 145 146 /** 147 * Sets the summary text shown when the item is expanded. Corresponds to the {@code 148 * app:sudExpandedSummary} XML attribute. 149 */ setExpandedSummary(CharSequence expandedSummary)150 public void setExpandedSummary(CharSequence expandedSummary) { 151 this.expandedSummary = expandedSummary; 152 if (isExpanded()) { 153 notifyChanged(); 154 } 155 } 156 157 @Override onBindView(View view)158 public void onBindView(View view) { 159 // TODO: If it is possible to detect, log a warning if this is being used with ListView. 160 super.onBindView(view); 161 View content = view.findViewById(R.id.sud_items_expandable_switch_content); 162 content.setOnClickListener(this); 163 164 if (content instanceof CheckableLinearLayout) { 165 CheckableLinearLayout checkableLinearLayout = (CheckableLinearLayout) content; 166 checkableLinearLayout.setChecked(isExpanded()); 167 168 // On lower versions 169 ViewCompat.setAccessibilityLiveRegion( 170 checkableLinearLayout, 171 isExpanded() 172 ? ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE 173 : ViewCompat.ACCESSIBILITY_LIVE_REGION_NONE); 174 175 ViewCompat.setAccessibilityDelegate(checkableLinearLayout, accessibilityDelegate); 176 } 177 178 tintCompoundDrawables(view); 179 180 // Expandable switch item has focusability on the expandable layout on the left, and the 181 // switch on the right, but not the item itself. 182 view.setFocusable(false); 183 184 LayoutStyler.applyPartnerCustomizationLayoutPaddingStyle(content); 185 } 186 187 @Override onClick(View v)188 public void onClick(View v) { 189 setExpanded(!isExpanded()); 190 } 191 192 // Tint the expand arrow with the text color tintCompoundDrawables(View view)193 private void tintCompoundDrawables(View view) { 194 final TypedArray a = 195 view.getContext().obtainStyledAttributes(new int[] {android.R.attr.textColorPrimary}); 196 final ColorStateList tintColor = a.getColorStateList(0); 197 a.recycle(); 198 199 if (tintColor != null) { 200 TextView titleView = (TextView) view.findViewById(R.id.sud_items_title); 201 for (Drawable drawable : titleView.getCompoundDrawables()) { 202 if (drawable != null) { 203 drawable.setColorFilter(tintColor.getDefaultColor(), Mode.SRC_IN); 204 } 205 } 206 if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { 207 for (Drawable drawable : titleView.getCompoundDrawablesRelative()) { 208 if (drawable != null) { 209 drawable.setColorFilter(tintColor.getDefaultColor(), Mode.SRC_IN); 210 } 211 } 212 } 213 } 214 } 215 } 216