More cleanups

- Use support libs to render vector animated lists pre 21
- Remove radio buttons as it uses pngs
- Remove unused resources
This commit is contained in:
topjohnwu 2020-08-10 19:21:49 -07:00
parent 46865ac6f4
commit ec3033dd81
46 changed files with 80 additions and 397 deletions

View File

@ -5,6 +5,7 @@ android {
defaultConfig {
applicationId "com.buildware.widget.sample"
buildToolsVersion rootProject.ext.buildToolsVersion
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1

View File

@ -3,15 +3,11 @@ package com.buildware.widget.sample;
import android.os.Bundle;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatCheckBox;
import com.topjohnwu.widget.IndeterminateCheckBox;
import com.topjohnwu.widget.IndeterminateRadioButton;
public class MainActivity extends AppCompatActivity {
@ -23,45 +19,11 @@ public class MainActivity extends AppCompatActivity {
final CheckBox checkBox = findViewById(R.id.checkBox);
final AppCompatCheckBox appCompatCheckBox = findViewById(R.id.app_compat_checkbox);
final IndeterminateCheckBox indetermCheckBox = findViewById(R.id.indeterm_checkbox);
final IndeterminateRadioButton radio = findViewById(R.id.indeterm_radio);
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
String stateText = isChecked ? "Checked" : "Unchecked";
Toast.makeText(MainActivity.this, "Standard CheckBox: " + stateText, Toast.LENGTH_SHORT).show();
}
});
appCompatCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
String stateText = isChecked ? "Checked" : "Unchecked";
Toast.makeText(MainActivity.this, "AppCompat CheckBox: " + stateText, Toast.LENGTH_SHORT).show();
}
});
indetermCheckBox.setOnStateChangedListener(new IndeterminateCheckBox.OnStateChangedListener() {
@Override
public void onStateChanged(IndeterminateCheckBox check, @Nullable Boolean state) {
String stateText = (state != null) ? (state ? "Checked" : "Unchecked") : "Indeterminate";
Toast.makeText(MainActivity.this, "IndeterminateCheckBox: " + stateText, Toast.LENGTH_SHORT).show();
}
});
radio.setOnStateChangedListener(new IndeterminateRadioButton.OnStateChangedListener() {
@Override
public void onStateChanged(IndeterminateRadioButton radioButton, @Nullable Boolean state) {
String stateText = (state != null) ? (state ? "Checked" : "Unchecked") : "Indeterminate";
Toast.makeText(MainActivity.this, "IndeterminateRadioButton: " + stateText, Toast.LENGTH_SHORT).show();
}
});
findViewById(R.id.btn_indeterminate).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
indetermCheckBox.setState(null);
radio.setState(null);
}
});
@ -71,7 +33,6 @@ public class MainActivity extends AppCompatActivity {
checkBox.setChecked(true);
appCompatCheckBox.setChecked(true);
indetermCheckBox.setChecked(true);
radio.setChecked(true);
}
});
@ -81,7 +42,6 @@ public class MainActivity extends AppCompatActivity {
checkBox.setChecked(false);
appCompatCheckBox.setChecked(false);
indetermCheckBox.setChecked(false);
radio.setChecked(false);
}
});
@ -91,7 +51,6 @@ public class MainActivity extends AppCompatActivity {
checkBox.setEnabled(true);
appCompatCheckBox.setEnabled(true);
indetermCheckBox.setEnabled(true);
radio.setEnabled(true);
}
});
@ -101,7 +60,6 @@ public class MainActivity extends AppCompatActivity {
checkBox.setEnabled(false);
appCompatCheckBox.setEnabled(false);
indetermCheckBox.setEnabled(false);
radio.setEnabled(false);
}
});
}

View File

@ -82,13 +82,5 @@
android:text="IndeterminateCheckBox"
app:indeterminate="true"/>
<com.topjohnwu.widget.IndeterminateRadioButton
android:id="@+id/indeterm_radio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="IndeterminateRadioButton"
app:indeterminate="true"
android:gravity="center_vertical"/>
</LinearLayout>
</ScrollView>

View File

@ -22,6 +22,7 @@ ext {
compileSdkVersion = 30
targetSdkVersion = 30
minSdkVersion = 14
buildToolsVersion = "30.0.1"
}
task clean(type: Delete) {

View File

@ -7,17 +7,24 @@ android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
buildToolsVersion rootProject.ext.buildToolsVersion
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0.6"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
buildFeatures {
buildConfig false
}
}
task sourcesJar(type: Jar) {

View File

@ -1,21 +1,26 @@
package com.topjohnwu.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.StateSet;
import android.view.ViewDebug;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.core.widget.CompoundButtonCompat;
import com.google.android.material.checkbox.MaterialCheckBox;
import com.google.android.material.color.MaterialColors;
/**
* A CheckBox with additional 3rd "indeterminate" state.
* By default it is in "determinate" (checked or unchecked) state.
* @author Svetlozar Kostadinov (sevarbg@gmail.com)
*/
public class IndeterminateCheckBox extends MaterialCheckBox implements IndeterminateCheckable {
public class IndeterminateCheckBox extends MaterialCheckBox {
private static final int[] INDETERMINATE_STATE_SET = {
R.attr.state_indeterminate
@ -51,12 +56,12 @@ public class IndeterminateCheckBox extends MaterialCheckBox implements Indetermi
public IndeterminateCheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Utils.setButtonDrawable(this, R.drawable.btn_checkmark);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.IndeterminateCheckable);
setButtonDrawable(R.drawable.btn_checkmark);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.IndeterminateCheckBox);
try {
// Read the XML attributes
final boolean indeterminate = a.getBoolean(
R.styleable.IndeterminateCheckable_indeterminate, false);
R.styleable.IndeterminateCheckBox_indeterminate, false);
if (indeterminate) {
setIndeterminate(true);
}
@ -105,14 +110,12 @@ public class IndeterminateCheckBox extends MaterialCheckBox implements Indetermi
}
}
@Override
@Nullable
@ViewDebug.ExportedProperty
public Boolean getState() {
return mIndeterminate ? null : isChecked();
}
@Override
public void setState(@Nullable Boolean state) {
if (state != null) {
setChecked(state);
@ -185,4 +188,38 @@ public class IndeterminateCheckBox extends MaterialCheckBox implements Indetermi
notifyStateListener();
}
}
@Override
public void setButtonDrawable(@DrawableRes int drawable) {
super.setButtonDrawable(drawable);
CompoundButtonCompat.setButtonTintList(this, createIndeterminateColorStateList());
}
private ColorStateList createIndeterminateColorStateList() {
final int[][] states = new int[][]{
new int[]{-android.R.attr.state_enabled},
new int[]{R.attr.state_indeterminate},
new int[]{android.R.attr.state_checked},
StateSet.WILD_CARD
};
int colorControlActivated = MaterialColors.getColor(this, R.attr.colorControlActivated);
int colorIndeterminate = MaterialColors.getColor(this, R.attr.colorControlIndeterminate, colorControlActivated);
int colorSurface = MaterialColors.getColor(this, R.attr.colorSurface);
int colorOnSurface = MaterialColors.getColor(this, R.attr.colorOnSurface);
int checked = MaterialColors.layer(colorSurface, colorControlActivated, MaterialColors.ALPHA_FULL);
int indeterminate = MaterialColors.layer(colorSurface, colorIndeterminate, MaterialColors.ALPHA_FULL);
int unchecked = MaterialColors.layer(colorSurface, colorOnSurface, MaterialColors.ALPHA_MEDIUM);
int disabled = MaterialColors.layer(colorSurface, colorOnSurface, MaterialColors.ALPHA_DISABLED);
final int[] colors = new int[]{
disabled,
indeterminate,
checked,
unchecked
};
return new ColorStateList(states, colors);
}
}

View File

@ -1,20 +0,0 @@
package com.topjohnwu.widget;
import android.widget.Checkable;
import androidx.annotation.Nullable;
/**
* Extension to Checkable interface with addition "indeterminate" state
* represented by <code>getState()</code>. Value meanings:
* null = indeterminate state
* true = checked state
* false = unchecked state
*/
public interface IndeterminateCheckable extends Checkable {
void setState(@Nullable Boolean state);
@Nullable
Boolean getState();
}

View File

@ -1,192 +0,0 @@
package com.topjohnwu.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.ViewDebug;
import androidx.annotation.Nullable;
import com.google.android.material.radiobutton.MaterialRadioButton;
/**
* A RadioButton with additional 3rd "indeterminate" state.
* By default it is in "determinate" (checked or unchecked) state.
* @author Svetlozar Kostadinov (sevarbg@gmail.com)
*/
public class IndeterminateRadioButton extends MaterialRadioButton
implements IndeterminateCheckable {
private static final int[] INDETERMINATE_STATE_SET = {
R.attr.state_indeterminate
};
private boolean mIndeterminate;
private boolean mBroadcasting;
private OnStateChangedListener mOnStateChangedListener;
/**
* Interface definition for a callback to be invoked when the checked state changed.
*/
public interface OnStateChangedListener {
/**
* Called when the indeterminate state has changed.
*
* @param radioButton The RadioButton whose state has changed.
* @param state The state of buttonView. Value meanings:
* null = indeterminate state
* true = checked state
* false = unchecked state
*/
void onStateChanged(IndeterminateRadioButton radioButton, @Nullable Boolean state);
}
public IndeterminateRadioButton(Context context) {
this(context, null);
}
public IndeterminateRadioButton(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.radioButtonStyle);
}
public IndeterminateRadioButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Utils.setButtonDrawable(this, R.drawable.btn_radio);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.IndeterminateCheckable);
try {
// Read the XML attributes
final boolean indeterminate = a.getBoolean(
R.styleable.IndeterminateCheckable_indeterminate, false);
if (indeterminate) {
setState(null);
}
} finally {
a.recycle();
}
}
@Override
public void toggle() {
if (mIndeterminate) {
setChecked(true);
} else {
super.toggle();
}
}
public void setChecked(boolean checked) {
final boolean checkedChanged = isChecked() != checked;
super.setChecked(checked);
final boolean wasIndeterminate = isIndeterminate();
setIndeterminateImpl(false, false);
if (wasIndeterminate || checkedChanged) {
notifyStateListener();
}
}
@ViewDebug.ExportedProperty
public boolean isIndeterminate() {
return mIndeterminate;
}
public void setIndeterminate(boolean indeterminate) {
setIndeterminateImpl(indeterminate, true);
}
private void setIndeterminateImpl(boolean indeterminate, boolean notify) {
if (mIndeterminate != indeterminate) {
mIndeterminate = indeterminate;
refreshDrawableState();
if (notify) {
/*notifyViewAccessibilityStateChangedIfNeeded(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); */
notifyStateListener();
}
}
}
@Override
@Nullable
@ViewDebug.ExportedProperty
public Boolean getState() {
return mIndeterminate ? null : isChecked();
}
@Override
public void setState(@Nullable Boolean state) {
if (state != null) {
setChecked(state);
} else {
setIndeterminate(true);
}
}
/**
* Register a callback to be invoked when the indeterminate or checked state changes.
* The standard <code>OnCheckedChangedListener</code> will still be called prior to
* OnStateChangedListener.
*
* @param listener the callback to call on indeterminate or checked state change
*/
public void setOnStateChangedListener(OnStateChangedListener listener) {
mOnStateChangedListener = listener;
}
@Override
public CharSequence getAccessibilityClassName() {
return IndeterminateRadioButton.class.getName();
}
private void notifyStateListener() {
// Avoid infinite recursions if state is changed from a listener
if (mBroadcasting) {
return;
}
mBroadcasting = true;
if (mOnStateChangedListener != null) {
mOnStateChangedListener.onStateChanged(this, getState());
}
mBroadcasting = false;
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (getState() == null) {
mergeDrawableStates(drawableState, INDETERMINATE_STATE_SET);
}
return drawableState;
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
IndeterminateSavedState ss = new IndeterminateSavedState(superState);
ss.indeterminate = mIndeterminate;
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
IndeterminateSavedState ss = (IndeterminateSavedState) state;
// This temporarily disallows our callback notification, we will call it below if needed
mBroadcasting = true;
super.onRestoreInstanceState(ss.getSuperState());
mBroadcasting = false;
mIndeterminate = ss.indeterminate;
// Both "indeterminate" and "checked" state are considered "excited" states. "Excited" state
// is state that is different from the default "unchecked". On view restoration CompoundButton
// notifies for change if the restored state is non-default. So, we will do the same for our merged state.
if (mIndeterminate || isChecked()) {
notifyStateListener();
}
}
}

View File

@ -10,9 +10,6 @@ import android.view.View;
class IndeterminateSavedState extends View.BaseSavedState {
boolean indeterminate;
/**
* Constructor called from {@link IndeterminateRadioButton#onSaveInstanceState()}
*/
IndeterminateSavedState(Parcelable superState) {
super(superState);
}

View File

@ -1,51 +0,0 @@
package com.topjohnwu.widget;
import android.content.res.ColorStateList;
import android.util.StateSet;
import android.view.View;
import android.widget.CompoundButton;
import androidx.annotation.DrawableRes;
import androidx.core.widget.CompoundButtonCompat;
import com.google.android.material.color.MaterialColors;
class Utils {
public static void setButtonDrawable(CompoundButton view, @DrawableRes int drawable) {
if (!(view instanceof IndeterminateCheckable)) {
throw new IllegalArgumentException("view must implement IndeterminateCheckable");
}
view.setButtonDrawable(drawable);
CompoundButtonCompat.setButtonTintList(view, createIndeterminateColorStateList(view));
}
private static ColorStateList createIndeterminateColorStateList(View view) {
final int[][] states = new int[][]{
new int[]{-android.R.attr.state_enabled},
new int[]{R.attr.state_indeterminate},
new int[]{android.R.attr.state_checked},
StateSet.WILD_CARD
};
int colorControlActivated = MaterialColors.getColor(view, R.attr.colorControlActivated);
int colorIndeterminate = MaterialColors.getColor(view, R.attr.colorControlIndeterminate, colorControlActivated);
int colorSurface = MaterialColors.getColor(view, R.attr.colorSurface);
int colorOnSurface = MaterialColors.getColor(view, R.attr.colorOnSurface);
int checked = MaterialColors.layer(colorSurface, colorControlActivated, MaterialColors.ALPHA_FULL);
int indeterminate = MaterialColors.layer(colorSurface, colorIndeterminate, MaterialColors.ALPHA_FULL);
int unchecked = MaterialColors.layer(colorSurface, colorOnSurface, MaterialColors.ALPHA_MEDIUM);
int disabled = MaterialColors.layer(colorSurface, colorOnSurface, MaterialColors.ALPHA_DISABLED);
final int[] colors = new int[]{
disabled,
indeterminate,
checked,
unchecked
};
return new ColorStateList(states, colors);
}
}

View File

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- The original name was btn_check_material_anim.xml -->
<item
android:id="@+id/indeterminate"
app:state_indeterminate="true"
android:drawable="@drawable/ic_checkbox_indeterminate" />
<item
android:id="@+id/checked"
android:state_checked="true"
android:drawable="@drawable/ic_checkbox_checked" />
<item
android:id="@+id/unchecked"
android:drawable="@drawable/ic_checkbox_unchecked" />
<transition
android:fromId="@+id/unchecked"
android:toId="@+id/checked"
android:drawable="@drawable/ic_checkbox_unchecked_to_checked_animation" />
<transition
android:fromId="@+id/checked"
android:toId="@+id/unchecked"
android:drawable="@drawable/ic_checkbox_checked_to_unchecked_animation" />
<transition
android:fromId="@+id/indeterminate"
android:toId="@+id/checked"
android:drawable="@drawable/ic_checkbox_indeterminate_to_checked_animation" />
<transition
android:fromId="@+id/checked"
android:toId="@+id/indeterminate"
android:drawable="@drawable/ic_checkbox_checked_to_indeterminate_animation" />
<transition
android:fromId="@+id/unchecked"
android:toId="@+id/indeterminate"
android:drawable="@drawable/ic_checkbox_unchecked_to_indeterminate_animation" />
<transition
android:fromId="@+id/indeterminate"
android:toId="@+id/unchecked"
android:drawable="@drawable/ic_checkbox_indeterminate_to_unchecked_animation" />
</animated-selector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android"
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- The original name was btn_check_material_anim.xml -->
@ -28,4 +28,28 @@
<item
android:id="@+id/unchecked"
android:drawable="@drawable/ic_checkbox_unchecked" />
</selector>
<transition
android:fromId="@+id/unchecked"
android:toId="@+id/checked"
android:drawable="@drawable/ic_checkbox_unchecked_to_checked_animation" />
<transition
android:fromId="@+id/checked"
android:toId="@+id/unchecked"
android:drawable="@drawable/ic_checkbox_checked_to_unchecked_animation" />
<transition
android:fromId="@+id/indeterminate"
android:toId="@+id/checked"
android:drawable="@drawable/ic_checkbox_indeterminate_to_checked_animation" />
<transition
android:fromId="@+id/checked"
android:toId="@+id/indeterminate"
android:drawable="@drawable/ic_checkbox_checked_to_indeterminate_animation" />
<transition
android:fromId="@+id/unchecked"
android:toId="@+id/indeterminate"
android:drawable="@drawable/ic_checkbox_unchecked_to_indeterminate_animation" />
<transition
android:fromId="@+id/indeterminate"
android:toId="@+id/unchecked"
android:drawable="@drawable/ic_checkbox_indeterminate_to_unchecked_animation" />
</animated-selector>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:constantSize="true">
<item app:state_indeterminate="true" android:drawable="@drawable/ic_radio_indeterminate" />
<item android:state_checked="true" android:drawable="@drawable/btn_radio_to_off_mtrl_000" />
<item android:drawable="@drawable/btn_radio_to_on_mtrl_000" />
</selector>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="IndeterminateCheckable">
<declare-styleable name="IndeterminateCheckBox">
<!-- Additional drawable state. It is merged into the parent CompoundButton states -->
<attr name="state_indeterminate" format="boolean" />
<!-- Indeterminate state attribute for the view -->

View File

@ -1,3 +0,0 @@
<resources>
<string name="app_name">Indeterminate CheckBox and RadioButton Library</string>
</resources>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item format="float" name="disabledAlpha" type="dimen">0.30</item>
</resources>