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