1919import com .google .android .material .R ;
2020
2121import static com .google .android .material .theme .overlay .MaterialThemeOverlay .wrap ;
22+ import static java .lang .Math .min ;
2223
2324import android .content .Context ;
2425import android .content .res .TypedArray ;
3637import androidx .annotation .IdRes ;
3738import androidx .annotation .NonNull ;
3839import androidx .annotation .Nullable ;
40+ import androidx .annotation .Px ;
3941import androidx .annotation .VisibleForTesting ;
4042import androidx .core .view .AccessibilityDelegateCompat ;
4143import androidx .core .view .MarginLayoutParamsCompat ;
4850import com .google .android .material .internal .ViewUtils ;
4951import com .google .android .material .shape .AbsoluteCornerSize ;
5052import com .google .android .material .shape .CornerSize ;
53+ import com .google .android .material .shape .RelativeCornerSize ;
5154import com .google .android .material .shape .ShapeAppearanceModel ;
5255import java .util .ArrayList ;
5356import java .util .Collections ;
107110 *
108111 * <p>Any {@link MaterialButton}s added to this view group are automatically marked as {@code
109112 * checkable}, and by default multiple buttons within the same group can be checked. To enforce that
110- * only one button can be checked at a time, set the {@code
111- * app:singleSelection} attribute to {@code true} on the MaterialButtonToggleGroup or call {@link
112- * #setSingleSelection(boolean) setSingleSelection(true)}.
113+ * only one button can be checked at a time, set the {@code app:singleSelection} attribute to {@code
114+ * true} on the MaterialButtonToggleGroup or call {@link #setSingleSelection(boolean)
115+ * setSingleSelection(true)}.
113116 *
114117 * <p>MaterialButtonToggleGroup is a {@link LinearLayout}. Using {@code
115118 * android:layout_width="MATCH_PARENT"} and removing {@code android:insetBottom} {@code
@@ -179,8 +182,10 @@ public int compare(MaterialButton v1, MaterialButton v2) {
179182 private boolean singleSelection ;
180183 private boolean selectionRequired ;
181184
182- @ IdRes
183- private final int defaultCheckId ;
185+ @ NonNull private CornerSize insideCornerSize ;
186+ @ Px private int spacing ;
187+
188+ @ IdRes private final int defaultCheckId ;
184189 private Set <Integer > checkedIds = new HashSet <>();
185190
186191 public MaterialButtonToggleGroup (@ NonNull Context context ) {
@@ -203,10 +208,16 @@ public MaterialButtonToggleGroup(
203208 setSingleSelection (
204209 attributes .getBoolean (R .styleable .MaterialButtonToggleGroup_singleSelection , false ));
205210 defaultCheckId =
206- attributes .getResourceId (
207- R .styleable .MaterialButtonToggleGroup_checkedButton , View .NO_ID );
211+ attributes .getResourceId (R .styleable .MaterialButtonToggleGroup_checkedButton , View .NO_ID );
208212 selectionRequired =
209213 attributes .getBoolean (R .styleable .MaterialButtonToggleGroup_selectionRequired , false );
214+ insideCornerSize =
215+ ShapeAppearanceModel .getCornerSize (
216+ attributes ,
217+ R .styleable .MaterialButtonToggleGroup_insideCornerSize ,
218+ new AbsoluteCornerSize (0 ));
219+ spacing =
220+ attributes .getDimensionPixelSize (R .styleable .MaterialButtonToggleGroup_android_spacing , 0 );
210221 setChildrenDrawingOrderEnabled (true );
211222 setEnabled (attributes .getBoolean (R .styleable .MaterialButtonToggleGroup_android_enabled , true ));
212223 attributes .recycle ();
@@ -313,7 +324,7 @@ public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo inf
313324 /* rowCount= */ 1 ,
314325 /* columnCount= */ getVisibleButtonCount (),
315326 /* hierarchical= */ false ,
316- /* selectionMode = */ isSingleSelection ()
327+ /* selectionMode= */ isSingleSelection ()
317328 ? CollectionInfoCompat .SELECTION_MODE_SINGLE
318329 : CollectionInfoCompat .SELECTION_MODE_MULTIPLE ));
319330 }
@@ -496,6 +507,29 @@ public void setSingleSelection(@BoolRes int id) {
496507 setSingleSelection (getResources ().getBoolean (id ));
497508 }
498509
510+ @ Px
511+ public int getSpacing () {
512+ return spacing ;
513+ }
514+
515+ public void setSpacing (@ Px int spacing ) {
516+ this .spacing = spacing ;
517+ invalidate ();
518+ requestLayout ();
519+ }
520+
521+ public void setInsideCornerSizeInPx (@ Px int px ) {
522+ insideCornerSize = new AbsoluteCornerSize (px );
523+ updateChildShapes ();
524+ invalidate ();
525+ }
526+
527+ public void setInsideCornerSizeInFraction (float fraction ) {
528+ insideCornerSize = new RelativeCornerSize (fraction );
529+ updateChildShapes ();
530+ invalidate ();
531+ }
532+
499533 private void setCheckedStateForView (@ IdRes int viewId , boolean checked ) {
500534 View checkedView = findViewById (viewId );
501535 if (checkedView instanceof MaterialButton ) {
@@ -528,18 +562,20 @@ private void adjustChildMarginsAndUpdateLayout() {
528562 MaterialButton previousButton = getChildButton (i - 1 );
529563
530564 // Calculates the margin adjustment to be the smaller of the two adjacent stroke widths
531- int smallestStrokeWidth =
532- Math .min (currentButton .getStrokeWidth (), previousButton .getStrokeWidth ());
565+ int smallestStrokeWidth = 0 ;
566+ if (spacing <= 0 ) {
567+ smallestStrokeWidth = min (currentButton .getStrokeWidth (), previousButton .getStrokeWidth ());
568+ }
533569
534570 LayoutParams params = buildLayoutParams (currentButton );
535571 if (getOrientation () == HORIZONTAL ) {
536572 MarginLayoutParamsCompat .setMarginEnd (params , 0 );
537- MarginLayoutParamsCompat .setMarginStart (params , - smallestStrokeWidth );
573+ MarginLayoutParamsCompat .setMarginStart (params , spacing - smallestStrokeWidth );
538574 params .topMargin = 0 ;
539575 } else {
540576 params .bottomMargin = 0 ;
541- params .topMargin = - smallestStrokeWidth ;
542- MarginLayoutParamsCompat .setMarginStart (params , 0 );
577+ params .topMargin = spacing - smallestStrokeWidth ;
578+ MarginLayoutParamsCompat .setMarginEnd (params , 0 );
543579 }
544580
545581 currentButton .setLayoutParams (params );
@@ -648,6 +684,7 @@ private int getIndexWithinVisibleButtons(@Nullable View child) {
648684 private CornerData getNewCornerData (
649685 int index , int firstVisibleChildIndex , int lastVisibleChildIndex ) {
650686 CornerData cornerData = originalCornerData .get (index );
687+ CornerData insideCornerData = new CornerData (insideCornerSize );
651688
652689 // If only one (visible) child exists, use its original corners
653690 if (firstVisibleChildIndex == lastVisibleChildIndex ) {
@@ -656,14 +693,18 @@ private CornerData getNewCornerData(
656693
657694 boolean isHorizontal = getOrientation () == HORIZONTAL ;
658695 if (index == firstVisibleChildIndex ) {
659- return isHorizontal ? CornerData .start (cornerData , this ) : CornerData .top (cornerData );
696+ return isHorizontal
697+ ? CornerData .start (cornerData , insideCornerData , this )
698+ : CornerData .top (cornerData , insideCornerData );
660699 }
661700
662701 if (index == lastVisibleChildIndex ) {
663- return isHorizontal ? CornerData .end (cornerData , this ) : CornerData .bottom (cornerData );
702+ return isHorizontal
703+ ? CornerData .end (cornerData , insideCornerData , this )
704+ : CornerData .bottom (cornerData , insideCornerData );
664705 }
665706
666- return null ;
707+ return insideCornerData ;
667708 }
668709
669710 private static void updateBuilderWithCornerData (
@@ -782,11 +823,11 @@ private void updateChildOrder() {
782823 }
783824
784825 void onButtonCheckedStateChanged (@ NonNull MaterialButton button , boolean isChecked ) {
785- // Checked state change is triggered by the button group, do not update checked ids again.
786- if (skipCheckedStateTracker ) {
787- return ;
788- }
789- checkInternal (button .getId (), isChecked );
826+ // Checked state change is triggered by the button group, do not update checked ids again.
827+ if (skipCheckedStateTracker ) {
828+ return ;
829+ }
830+ checkInternal (button .getId (), isChecked );
790831 }
791832
792833 /**
@@ -814,49 +855,56 @@ public void onPressedChanged(@NonNull MaterialButton button, boolean isPressed)
814855
815856 private static class CornerData {
816857
817- private static final CornerSize noCorner = new AbsoluteCornerSize (0 );
858+ @ NonNull CornerSize topLeft ;
859+ @ NonNull CornerSize topRight ;
860+ @ NonNull CornerSize bottomRight ;
861+ @ NonNull CornerSize bottomLeft ;
818862
819- CornerSize topLeft ;
820- CornerSize topRight ;
821- CornerSize bottomRight ;
822- CornerSize bottomLeft ;
863+ CornerData (@ NonNull CornerSize cornerSize ) {
864+ this (cornerSize , cornerSize , cornerSize , cornerSize );
865+ }
823866
824867 CornerData (
825- CornerSize topLeft , CornerSize bottomLeft , CornerSize topRight , CornerSize bottomRight ) {
868+ @ NonNull CornerSize topLeft ,
869+ @ NonNull CornerSize bottomLeft ,
870+ @ NonNull CornerSize topRight ,
871+ @ NonNull CornerSize bottomRight ) {
826872 this .topLeft = topLeft ;
827873 this .topRight = topRight ;
828874 this .bottomRight = bottomRight ;
829875 this .bottomLeft = bottomLeft ;
830876 }
831877
832878 /** Keep the start side of the corner original data */
833- public static CornerData start (CornerData orig , View view ) {
834- return ViewUtils .isLayoutRtl (view ) ? right (orig ) : left (orig );
879+ public static CornerData start (
880+ @ NonNull CornerData orig , @ NonNull CornerData other , @ NonNull View view ) {
881+ return ViewUtils .isLayoutRtl (view ) ? right (orig , other ) : left (orig , other );
835882 }
836883
837884 /** Keep the end side of the corner original data */
838- public static CornerData end (CornerData orig , View view ) {
839- return ViewUtils .isLayoutRtl (view ) ? left (orig ) : right (orig );
885+ public static CornerData end (
886+ @ NonNull CornerData orig , @ NonNull CornerData other , @ NonNull View view ) {
887+ return ViewUtils .isLayoutRtl (view ) ? left (orig , other ) : right (orig , other );
840888 }
841889
842890 /** Keep the left side of the corner original data */
843- public static CornerData left (CornerData orig ) {
844- return new CornerData (orig .topLeft , orig .bottomLeft , noCorner , noCorner );
891+ public static CornerData left (@ NonNull CornerData orig , @ NonNull CornerData other ) {
892+ return new CornerData (orig .topLeft , orig .bottomLeft , other . topRight , other . bottomRight );
845893 }
846894
847895 /** Keep the right side of the corner original data */
848- public static CornerData right (CornerData orig ) {
849- return new CornerData (noCorner , noCorner , orig .topRight , orig .bottomRight );
896+ public static CornerData right (@ NonNull CornerData orig , @ NonNull CornerData other ) {
897+ return new CornerData (other . topLeft , other . bottomLeft , orig .topRight , orig .bottomRight );
850898 }
851899
852900 /** Keep the top side of the corner original data */
853- public static CornerData top (CornerData orig ) {
854- return new CornerData (orig .topLeft , noCorner , orig .topRight , noCorner );
901+ public static CornerData top (@ NonNull CornerData orig , @ NonNull CornerData other ) {
902+ return new CornerData (orig .topLeft , other . bottomLeft , orig .topRight , other . bottomRight );
855903 }
856904
857905 /** Keep the bottom side of the corner original data */
858- public static CornerData bottom (CornerData orig ) {
859- return new CornerData (noCorner , orig .bottomLeft , noCorner , orig .bottomRight );
906+ public static CornerData bottom (@ NonNull CornerData orig , @ NonNull CornerData other ) {
907+ return new CornerData (other . topLeft , orig .bottomLeft , other . topRight , orig .bottomRight );
860908 }
861909 }
862910}
0 commit comments