TextView Button RadioButton
public class Button extends TextView {
public Button(Context context) {
this(context, null);
}
public Button(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.buttonStyle);
}
public Button(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public Button(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public CharSequence getAccessibilityClassName() {
return Button.class.getName();
}
}
TextView 和 Button 区别,一个用了 textViewStyle,一个用了 buttonStyle。
CompoundButton 是 RadioButton 的父类 ,它和 Button 的区别:
View 的 onTouchEvent 会调用 performClick。
@Override
public boolean performClick() {
toggle();
// ...
}
public void toggle() {
setChecked(!mChecked);
}
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
refreshDrawableState();
notifyViewAccessibilityStateChangedIfNeeded(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
// Avoid infinite recursions if setChecked() is called from a listener
if (mBroadcasting) {
return;
}
mBroadcasting = true;
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
}
if (mOnCheckedChangeWidgetListener != null) {
mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
}
mBroadcasting = false;
}
}
RadioButton 重写了 toggle ,只允许选中:
@Override
public void toggle() {
// we override to prevent toggle when the radio is already
// checked (as opposed to check boxes widgets)
if (!isChecked()) {
super.toggle();
}
}
为什么 RadioButton 是可选中的,Button 确不行:
@Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
return drawableState;
}
而 RadioGroup 就是可以监听选中并清除其他子 RadioButton 选中的 LinearLayout。
----------------------------------------------------------------------------------------- 进入正题
https://github.com/Kaopiz/android-segmented-control
很棒的库,虽说不要重复造轮子,但我认为摸索是必要的。
利用之前的知识,外加 RadioButton 隐藏 ButtonDrawable 后,很容易做到。
除了 setCornerRaduis ,还有更细致的 setCornRadii 可用,然后:
有点像样了,还有个问题,相邻的 stroke 应该只显示一条。
我是没想到怎么解决,看看作者的做法:
layoutParams.setMargins(-strokeWidth, 0, 0, 0);
真是太聪明了。
RadioGroup 在 onMeasure 和 onLayout 方面其实就是 LinearLayout。
LinearLayout 的 onMeasure ,依赖于 child view 的 onMeasure 结果。
缩小 child view 是无济于事的,drawable 重画时会随 View 扩展。
这里的做法,是把 “选项二” 和 “选项三” 向左平移了 strokeWidth。
onMeasure 时总长度,确实减少了 leftMargin。
mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin +
lp.rightMargin + getNextLocationOffset(child));
onLayout 时向确实向左偏了 leftMargin,但 child 大小是不变的。
childLeft += lp.leftMargin;
setChildFrame(child, childLeft + getLocationOffset(child), childTop,
childWidth, childHeight);
最终结果图:
开源库中还是使用了矢量字体,TransitionDrawable 渐变切换,还加了按下的 maskDrawable 的效果等等。
int maskColor = Color.argb(50, Color.red(mTintColor), Color.green(mTintColor), Color.blue(mTintColor));
maskDrawable.setColor(maskColor);
public class IOSRadioGroup extends RadioGroup {
private int cornerRadius;
private int mainColor;
final int strokeWidth = 2;
float[] leftCornerRadii;
float[] middleCornerRadii;
float[] rightCornerRadii;
//
public IOSRadioGroup(Context context) {
super(context);
}
public IOSRadioGroup(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundCornerTextView);
float dp = typedArray.getDimension(R.styleable.RoundCornerTextView_rc_corner_radius, 0.0f);
cornerRadius = dp2px(context, dp);
leftCornerRadii = new float[]{cornerRadius, cornerRadius, 0, 0, 0, 0, cornerRadius, cornerRadius};
middleCornerRadii = new float[]{0, 0, 0, 0, 0, 0, 0, 0};
rightCornerRadii = new float[]{0, 0, cornerRadius, cornerRadius, cornerRadius, cornerRadius, 0, 0};
mainColor = typedArray.getColor(R.styleable.RoundCornerTextView_rc_background_color, Color.TRANSPARENT);
typedArray.recycle();
}
//
@Override
protected void onFinishInflate() {
super.onFinishInflate();
init();
}
//
@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
View first = getChildAt(0);
if (child != first) {
LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
// Third Point
// child 的大小没有变化,但位置会发生了变化,有了些许重叠。
layoutParams.setMargins(-strokeWidth, 0, 0, 0);
}
super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
}
//
private void init() {
for (int i = 0; i != getChildCount(); i++) {
RadioButton child = (RadioButton) getChildAt(i);
// Second Point
// 不同 child 的 stroke 形状不同
float[] radii;
if (i == 0) {
radii = leftCornerRadii;
} else if (i == getChildCount() - 1) {
radii = rightCornerRadii;
} else {
radii = middleCornerRadii;
}
changeChildBackground(child, radii);
}
}
//
private void changeChildBackground(RadioButton radioButton, float[] cornerRadii) {
GradientDrawable unchecked = new GradientDrawable();
unchecked.setCornerRadii(cornerRadii);
unchecked.setColor(Color.WHITE);
unchecked.setStroke(strokeWidth, mainColor);
GradientDrawable checked = new GradientDrawable();
checked.setCornerRadii(cornerRadii);
checked.setColor(mainColor);
StateListDrawable background = new StateListDrawable();
background.addState(new int[]{-android.R.attr.state_checked}, unchecked);
background.addState(new int[]{android.R.attr.state_checked}, checked);
radioButton.setBackground(background);
// First Point
// 把默认的 style 中的 ButtonDrawable 去除。
radioButton.setButtonDrawable(null);
ColorStateList colorStateList = new ColorStateList(
new int[][]{{-android.R.attr.state_checked}, {android.R.attr.state_checked}},
new int[]{mainColor, Color.WHITE}
);
radioButton.setTextColor(colorStateList);
}
//
protected int dp2px(Context context, float dp) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
}