I am using Material Components for creating the Choice chip. I have followed https://material.io/develop/android/components/chip/ document. There is enough stuff for creating a chip in XML but not get an idea of how to create choice chip programmatically.
I have used following code to creating chip dynamically but it creates action chip by default.
val chip = Chip(activity)
chip.text = ("Chip 1")
chipGpRow.addView(chip)
Check the Mike comment.
Otherwise you can define a simple layout (layout_chip_choice.xml) with the Chip and the style:
<com.google.android.material.chip.Chip
xmlns:android="http://schemas.android.com/apk/res/android"
style="#style/Widget.MaterialComponents.Chip.Choice"
.../>
Then use in your code:
val chip = inflater.inflate(R.layout.layout_chip_choice, chipGpRow, false) as Chip
chip.text = ("Chip 1")
chipGpRow.addView(chip)
following is my code, hope its useful to you :
create item xml for chips and add style that you want
like here style="#style/Widget.MaterialComponents.Chip.Choice"
item_chip_category.xml
<com.google.android.material.chip.Chip xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="#font/popin"
android:gravity="center"
android:paddingLeft="8dp"
android:paddingRight="8dp"
style="#style/Widget.MaterialComponents.Chip.Choice"
android:textAppearance="?android:attr/textAppearance"
android:textColor="#color/secondaryTextColor"
app:chipBackgroundColor="#color/colorAccent" />
activity.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="#font/popin"
android:padding="8dp"
android:text="Python Progrgrams"
android:textAlignment="center"
android:textAppearance="#style/TextAppearance.AppCompat.Medium"
android:textColor="#color/secondaryTextColor"
android:textStyle="bold" />
<com.google.android.material.chip.ChipGroup
android:id="#+id/chipsPrograms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="#dimen/text_margin"
android:paddingStart="#dimen/text_margin"
android:paddingEnd="#dimen/text_margin"
app:chipSpacing="8dp"
app:singleSelection="false">
</com.google.android.material.chip.ChipGroup>
</LinearLayout>
Activity.java
public void setCategoryChips(ArrayList<String> categorys) {
for (String category :
categorys) {
Chip mChip = (Chip) this.getLayoutInflater().inflate(R.layout.item_chip_category, null, false);
mChip.setText(category);
int paddingDp = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 10,
getResources().getDisplayMetrics()
);
mChip.setPadding(paddingDp, 0, paddingDp, 0);
mChip.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
}
});
chipsPrograms.addView(mChip);
}
}
You could either 1) create an xml layout for a chip that has the choice style and inflate it in code, similar to the ChipGroupDemoFragment example in the catalog: github.com/material-components/material-components-android/blob/… 2) create a custom theme that sets the default chipStyle to be #style/Widget.MaterialComponents.Chip.Choice I recommend #1 because it allows you the flexibility of dynamically creating chips of multiple styles.
XML
<com.google.android.material.chip.ChipGroup
android:id="#+id/chipGroupEmail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Activity.java
//init
ChipGroup chipGroupEmail = findViewById(R.id.chipGroupEmail);
//create a function
private void funAddChip(String name) {
Chip chip = new Chip(requireContext());
chip.setText(name);
chip.setCloseIconVisible(true);
chip.setMinWidth(100);
chip.setOnCloseIconClickListener(view -> {
try {
chipGroupEmail.removeView(view);
} catch (Exception e) {
Utils.Log_d("funAddChip Exception:-" + e);
}
});
chipGroupEmail.addView(chip);
}
I don't like the comment on setting the checkable attribute manually instead of the stylistic way. You lose other attributes, like ripple color and state list animator. Inflating a layout with your chip defined and including the style attribute with choice is the only way to do it as of now.
I have been trying to apply a style to a Chip created programmatically with context theme wrapper. This seems like the way to go, but Chip as an extension of AppCompatCheckBox does not define the four-argument constructor where the default style (action) could be overwritten with choice in my case. So the chip is always of type action ><.
Related
I'm using Material Chip and I want to show closeIcon only when the Chip is selected.
How can I achieve this behavior? I can do some binding etc, but I would prefer some simplier solution.
<com.google.android.material.chip.ChipGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipSpacing="8dp"
app:singleLine="true"
app:singleSelection="false">
<com.google.android.material.chip.Chip
android:id="#+id/chip_1"
style="#style/MyChipStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:closeIcon="#drawable/ic_cross_white_24dp"
app:closeIconEnabled="true"
app:checkedIconEnabled="false"
android:checkable="true"
android:text="Example text" />
</com.google.android.material.chip.ChipGroup>
You need to add code for checking that chip is selected or not.
Try below code to check:
private void chipCloseBtnVisi(){
if(chip_1.isChecked()){
// If Chip is selected(checked) then show close icon
chip_1.setCloseIconVisible(true);
}else{
// If Chip is not selected(checked) then hide close icon
chip_1.setCloseIconVisible(false);
}
}
To change check state, need to add setOnCheckedChangeListener to chip
chip_1.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
chipCloseBtnVisi();
}
});
I want to set button style programmatically. Reason for that is I have CustomView containing Button. I send style id to my CustomView using custom parameter inside <declare-styleable>. Now I need to set button style for only Button not whole view in my CustomView.
CustomView:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ProgressBar
android:id="#+id/progressBar"
android:layout_width="40dp"
android:layout_height="40dp"
android:indeterminate="true"
android:layout_gravity="center"
android:indeterminateTint="#color/colorText"/>
<Button
android:id="#+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</FrameLayout>
CustomView in my code:
<myproject.CustomView
android:id="#+id/btn"
app:progressColor="#color/colorText"
app:l_buttonText="#string/text"
app:l_buttonStyle="#style/dialog_outlined"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="48dp" />
What I want to achieve is to set style for Button only (value obtained from app:l_buttonStyle) But I have to do this programmatically, because if I set style in XML it will be applied to FrameLayout as a root view. Here is function to init parts of my CustomView
private fun init(context: Context) {
//Init default values
View.inflate(context, R.layout.loading_button, this)
button = findViewById(R.id.button)
progressBar = findViewById(R.id.progressBar)
progressBar?.setGone()
//Now I need to set style for my inflated Button from XML layout
}
The views in custom view has to be created programmatically and at the time of initializing the view custom style has to be applied. Once the view is initialized, style cannot be set.
val button = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Button(this, null, 0, R.style.BtnStyle)
} else {
var themeWrapper = ContextThemeWrapper(this, R.style.BtnStyle)
Button(themeWrapper, null, 0)
}
I was searching over the internet for how to perform the new cool android data-binding over the RadioGroup and I didn't find a single blog post about it.
Its a simple scenario, based on the radio button selected, I want to attach a callback event using android data binding. I don't find any method on the xml part which allows me to define a callback.
Like here is my RadioGroup:
<RadioGroup
android:id="#+id/split_type_radio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:checkedButton="#+id/split_type_equal"
android:gravity="center"
<!-- which tag ? -->
android:orientation="horizontal">
...
</RadioGroup>
How do I attach a handler method which will be called on RadioGroup's checkChnged event will fire using data-binding?
I have tried using onClick (don't know if it is the same) in layout file and defining method in the Activity and located it using this in the layout file:
<variable
name="handler"
type="com.example.MainActivity"/>
...
<RadioGroup
android:onClick="handler.onCustomCheckChanged"
.. >
And defined method onCustomCheckChanged like this:
public void onCustomCheckChanged(RadioGroup radio, int id) {
// ...
}
But, it gives me the compilation error:
Error:(58, 36) Listener class android.view.View.OnClickListener with method onClick did not match signature of any method handler.onCustomCheckChanged
I have seen many blogs mentioning it is possible with RadioGroup but non of them really say how. How can I handle this with data-binding ?
After digging to the bunch of methods, I found this question on SO which helped me understand how to bind single methods of listeners.
Here is what to do with RadioGroup:
In RadioGroup listener you have a method onCheckedChanged(RadioGroup g, int id). So you can directly bound that method to your handler or your activity by passing an instance of it as a variable in layout file and calling a method with the same signature.
So call on layout file like this:
<RadioGroup
android:id="#+id/split_type_radio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:checkedButton="#+id/split_type_equal"
android:gravity="center"
android:onCheckedChanged="#{handler.onSplitTypeChanged}"
android:orientation="horizontal">
...
</RadioGroup>
And in my activity or handler, I need to simply provide the method with same name and signature like this:
public void onSplitTypeChanged(RadioGroup radioGroup,int id) {
// ...
}
Just make sure method is public.
NOTE: This works for any (most of, I have not tried all) listener methods. Like for EditText you can provide android:onTextChanged and so on.
I am using a string, and in this case I have bindable based on viewModel.getCommuteType() viewModel.setCommuteType(String)
<RadioGroup
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RadioButton
android:checked="#{viewModel.commuteType.equals(Commute.DRIVING)}"
android:onClick="#{()->viewModel.setCommuteType(Commute.DRIVING)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="D"/>
<RadioButton
android:checked="#{viewModel.commuteType.equals(Commute.BICYCLE)}"
android:onClick="#{()->viewModel.setCommuteType(Commute.BICYCLE)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="B"/>
<RadioButton
android:checked="#{viewModel.commuteType.equals(Commute.WALKING)}"
android:onClick="#{()->viewModel.setCommuteType(Commute.WALKING)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="W"/>
<RadioButton
android:checked="#{viewModel.commuteType.equals(Commute.BUS)}"
android:onClick="#{()->viewModel.setCommuteType(Commute.BUS)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="T"/>
After some hours I found easy way: two-way databinding in android. It's base skeleton with livedata and Kotlin. Also you can use ObservableField()
Set your viewmodel to data
Create your radiogroup with buttons as you like. Important: set all radio buttons id !!!
Set in your radio group two-way binding to checked variable (use viewmodel variable)
Enjoy)
layout.xml
<data>
<variable
name="VM"
type="...YourViewModel" />
</data>
<LinearLayout
android:id="#+id/settings_block_env"
...
>
<RadioGroup
android:id="#+id/env_radioGroup"
android:checkedButton="#={VM.radio_checked}">
<RadioButton
android:id="#+id/your_id1"/>
<RadioButton
android:id="#+id/your_id2" />
<RadioButton
android:id="#+id/your_id3"/>
<RadioButton
android:id="#+id/your_id4"/>
</RadioGroup>
</LinearLayout>
class YourViewModel(): ViewModel {
var radio_checked = MutableLiveData<Int>()
init{
radio_checked.postValue(R.id.your_id1)//def value
}
//other code
}
Often you care more about what was actually checked instead of "something was checked". In such case alternative solution is to ignore RadioGroup and bind all items as below:
<RadioGroup (...) >
<RadioButton (...)
android:checked="#={viewModel.optionA}"/>
<RadioButton (...)
android:checked="#={viewModel.optionB}"/>
<RadioButton (...)
android:checked="#={viewModel.optionC}"/>
</RadioGroup>
where optionA, optionB and optionC are defined in ViewModel like below:
public final ObservableBoolean optionA = new ObservableBoolean();
public final ObservableBoolean optionB = new ObservableBoolean();
public final ObservableBoolean optionC = new ObservableBoolean();
This is usually enough, however if you want to react immediately on click then you can add callBacks and use them like that:
OnPropertyChangedCallback userChoosedA = new OnPropertyChangedCallback() {
#Override
public void onPropertyChanged(Observable sender, int propertyId) {
(...) // basically propertyId can be ignored in such case
}
};
optionA.addOnPropertyChangedCallback(userChoosedA);
Advantage of such approach is that you don't need to compare and track "id".
In my current project, I did it like this.
I have three currency in the project and I choose one of them via RadioGroup.
It's enum with currencies:
enum class Currency(val value: Byte) {
USD(0),
EUR(1),
RUB(2);
companion object Create {
fun from(sourceValue: Byte): Currency = values().first { it.value == sourceValue }
fun from(sourceValue: String): Currency = values().first { it.toString() == sourceValue }
}
}
A piece of my ViewModel:
class BaseCurrencyViewModel : ViewModelBase<BaseCurrencyModelInterface>() {
/**
* Selected currency
*/
val currency: MutableLiveData<Currency> = MutableLiveData()
/**
*
*/
init {
currency.value = Currency.USD // Init value
}
}
Part of my layout (pay attention to binding in RadioGroup and tags of RadioButton):
<RadioGroup
android:id="#+id/currencySwitchers"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:selectedCurrency = "#{viewModel.currency}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toEndOf="parent">
<RadioButton
android:id="#+id/usdSwitcher"
android:text="USD"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:tag="USD"
/>
<RadioButton
android:id="#+id/eurSwitcher"
android:text="EUR"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:tag="EUR"
/>
<RadioButton
android:id="#+id/rubSwitcher"
android:text="RUB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:tag="RUB"
/>
</RadioGroup>
And the final part - binding adapter.
#BindingAdapter("selectedCurrency")
fun setSelectedCurrency(view: View, value: MutableLiveData<Currency>?) {
view.getParentActivity()?.let { parentActivity ->
value?.observe(parentActivity, Observer { value ->
view.findViewWithTag<RadioButton>(value.toString())
?.also {
if(!it.isChecked) {
it.isChecked = true
}
}
}
)
(view as RadioGroup).setOnCheckedChangeListener { radioGroup, checkedId ->
val currency = Currency.from(radioGroup.findViewById<RadioButton>(checkedId).tag as String)
if(value != null && value.value != currency) {
value.value = currency
}
}
}
}
In this way, I got two-way binding between RadioGroup and a property in my ViewModel.
I am creating a custom compound view with the following layout
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="#+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="#+id/edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="text"
android:singleLine="true"/>
</merge>
As you can see, it is simply a TextView and a EditText. I want to be able to provide attributes to my custom view that are forwarded on to either the TextView or EditText. For example
<codeguru.labelededittext.LabeledEditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:label="#string/label"
app:hint="#string/hint"/>
I have figured out how to forward these string attributes to the TextView and EditText, repsectively:
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LabeledEditText,
0, 0);
try {
label.setText(a.getString(R.styleable.LabeledEditText_label));
edit.setHint(a.getString(R.styleable.LabeledEditText_hint));
} finally {
a.recycle();
}
Now I also want to set the inputType of the EditText. If I create a <attr name="inputType" format="flag"> tag, will I have to populate it with all the possible flag values? Is there a way to reuse the values already declared by EditText?
You can get this with:
int[] values = new int[]{android.R.attr.inputType};
TypedArray standardAttrArray = getContext().obtainStyledAttributes(attrs, values);
try {
mInputType = standardAttrArray.getInt(0, EditorInfo.TYPE_NULL);
} finally {
standardAttrArray.recycle();
}
OK, I have a complete layout built; however, I am not really pleased with the long xml file that has resulted. I have a shorted version of the xml outline and designer view below. And I was wondering how I can abstract out each group of similar components into their own custom control.
For example, in the picture below, I have highlighted one such control that I would like to abstract out. Instead of it being a LinearLayout with 2 TextView's inside with their own properties and attributes set. I would like to reference it via <package-name.individual_song_item
android:layout...> ... </>. All I would have to do is set the first TextView's text along with the second one via attributes in the top-level component.
How can this be done? I have the layout done and complete, but I don't like that nothing is abstracted away.
So the expected results that I am looking for are (if you look at the right-side of the image. there would only be one LinearLayout below the image, and the rest would be <package-name.individual_song_item>)
I have tried to just create a new layout xml with just the subsets of components, but I was not able to make it work when combining it back.
OLD WAY
<LinearLayout >
<ImageView />
<LinearLayout >
<LinearLayout >
<TextView />
<TextView />
</LinearLayout>
<LinearLayout >
<TextView />
<TextView />
</LinearLayout>
<LinearLayout >
<TextView />
<TextView />
</LinearLayout>
....
</LinearLayout>
</LinearLayout>
POSSIBLE PROPOSED WAY
<LinearLayout >
<ImageView />
<LinearLayout >
<com.example.individual_song_item />
<com.example.individual_song_item />
<com.example.individual_song_item />
....
<com.example.individual_song_item <!-- example (possible!?!?) -->
....
app:label="Group"
app:value="Group Name" />
</LinearLayout>
</LinearLayout>
Create a custom layout eg.
public class IndividualSongItem extends LinearLayout {
private String mSong;
private String mSongName;
public IndividualSongItem(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public IndividualSongItem(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.IndividualSongItem);
try {
// Read in your custom layout's attributes,
// for example song and songName text attributes
CharSequence s = a.getString(R.styleable.IndividualSongItem_song);
if (s != null) {
setSong(s.toString());
}
s = a.getString(R.styleable.IndividualSongItem_songName);
if (s != null) {
setSongName(s.toString());
}
} finally {
a.recycle();
}
}
....etc
You will also need to create an attributes XML for your new layout class.
For a full example of how to do what you're after look at the LabelView example in the ApiDemos.
It's also very well explained here.