I need to make a multiline text material button.
I have followed this question's answers, but it turns out that material buttons work differently.
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="#+id/toggle_parent_child"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:theme="#style/Theme.MaterialComponents"
android:visibility="gone"
app:checkedButton="#id/button_parent"
app:singleSelection="true">
<com.google.android.material.button.MaterialButton
android:id="#+id/button_parent"
style="#style/Login.Button.ToggleButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Parent\n Device" />
<com.google.android.material.button.MaterialButton
android:id="#+id/button_child"
style="#style/Login.Button.ToggleButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Child \n Device" />
I'm getting this:
but I need every word to be in a different line like this:
It might be late but here is my implementation to fix it
class MonsterButtonToggleGroup : MaterialButtonToggleGroup {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
super.addView(child, index, params)
if (child is MaterialButton)
child.maxLines = 2
}
}
you can use replace to make it a multiline string in you MaterialButton like this -
val singleLine = getString(R.string.hello_blank_activity)
demoText.text = singleLine
val multiLine = singleLine.replace(" ", "\n")
demoTextMulti.text = multiLine
Here is the screenshot of the output -
Related
I have a custom view like this:
class SquareView : View {
private lateinit var mRectanglePaint: Paint
constructor(context: Context?) : super(context) {
init(null)
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
init(attrs)
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init(attrs)
}
constructor(
context: Context?,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {
init(attrs)
}
private fun init(attrs: AttributeSet?) {
mRectanglePaint = Paint().apply {
isAntiAlias = true
color = Color.RED
}
}
override fun onDraw(canvas: Canvas) {
canvas.apply {
drawRectangle(this)
}
}
private fun drawRectangle(canvas: Canvas) {
val rect = Rect()
rect.left = 100
rect.right = rect.left + 100
rect.top = 100
rect.bottom = rect.top + 100
canvas.drawRect(rect, mRectanglePaint)
}
}
And, this is my blue_square.xml file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="wrap_content">
<View
android:id="#+id/blue_square"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#0000FF" />
</LinearLayout>
I want to add this XML layout to my view and set position to bottom of the red square.
I added this code but this time only the blue square appears.
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view = inflater.inflate(R.layout.blue_square, this, true)
I want to include the blue square in my custom view and change its position.
How can I do that? Thank you for your help.
When I said 2 different approaches, so the first is in a bit more detail but I'll also outline the others.
1)The first is to inflate and layout the xml to a Bitmap and then draw that Bitmap to you Canvas in the right place.
Inflate your xml BUT don't attach it or parent it to your custom view (as that really replaces you custom view (second and third parameters of inflate)
e.g.
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val xmlView = inflater.inflate(R.layout.blue_square, null, false)
Draw this new view to Bitmap
Sorry this is Java but you can convert it to Kotlin as you seem to be using that.
Canvas bitmapCanvas = new Canvas();
Bitmap bitmap = Bitmap.createBitmap(xmlView.getWidth(), xmlView.getHeight(), Bitmap.Config.ARGB_8888);
bitmapCanvas.setBitmap(bitmap);
xmlView.draw(bitmapCanvas);
Then in your onDraw draw this Bitmap to your canvas in the right position (setting top and left location to be below your red square)
Something like
plainPaint = Paint().apply { isAntiAlias = true }
canvas.drawBitmap (bitmap, left, top, plainPaint)
Note this is not exactly tested but it is similar to something I do a lot.
2)Don't invert the normal hierarchy of Views and ViewGroups, don't make your SquareView custom View be the single view your an App draws. Your SquareView custom view should just draw a view big enough to be the square (You should implement onMeasure for your custom view).
Then an App could do in it's xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="wrap_content">
<SquareView
android:id="#+id/custom_square"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<View
android:id="#+id/blue_square"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#0000FF" />
</LinearLayout>
Using:
com.google.android.material:material:1.4.0
If I have a layout xml file in a fragment/activity with a TextView:
<TextView
style="#style/TextAppearance.MaterialComponents.Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
If I go between light/dark mode the text color switches appropriately.
However if I create a custom view:
class MyView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyle, defStyleRes) { ... }
And that view contains the same textview, the text color does not change in night mode.
I initialize the view from a fragment:
new MyView(this.getActivity().getBaseContext());
I have also tried to directly apply the base theme:
new MyView(this.getActivity().getBaseContext(), null, R.id.AppTheme);
In addition for some strange reason I can work around this issue by creating my own text colors in the appropriate light/dark folders and that picks up the change between light/dark:
<TextView
style="#style/TextAppearance.MaterialComponents.Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/my_text_color" />
Turns out I was passing the wrong context.
When creating the view:
new MyView(this.getActivity().getBaseContext());
Should be:
new MyView(this.getActivity());
How can I make last visible items of recycler view to have a slight different style. For example, I would like them to appear like this:
As you can see, the last 3 items are grayed out proportionally. I have searched the internet but no success. Any suggestions would be greatly appreciated!
Find out the size of the list you are using to populate the recyler view, this may have been set in the adapter constructor. I'll call it myList.
then in your onBindViewHolder something like:
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
....
if (myList.size() - position < 3) {
holder.wordRow.setTextColor(this.mContext.getResources().getColor(R.color.light_gray));
}
Where wordRow is a TextView element
You can create a custom recyclerView layout and then set the topFadingEdgeLength to 0.0. This will allow you to only give effect in the bottom, else it will be applied to the top also.
In your XML
<com.example.BottomFadeEdgeRecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadingEdge="vertical"
android:fadingEdgeLength="180dp"
android:requiresFadingEdge="vertical">
</com.example.BottomFadeEdgeRecyclerView>
I have set the fadingEdgeLength to 180dp. You can set the desired value
And the custom view class
import android.content.Context
import android.util.AttributeSet
import androidx.recyclerview.widget.RecyclerView
class BottomFadeEdgeRecyclerView : RecyclerView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun getTopFadingEdgeStrength(): Float {
return 0.0f
}
}
you can create a custom scrollView layout and then set the topFadingEdgeLength to 0.0. This will allow you to only give effect in the bottom, else it will be applied to the top also.
You can use the scrollView to wrap your recyclerView
In the XML set your scrollView as below
<com.example.BottomFadeEdgeScrollView
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadingEdge="vertical"
android:fadingEdgeLength="180dp"
android:requiresFadingEdge="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.example.BottomFadeEdgeScrollView>
I have set the fadingEdgeLength to 180dp. You can set the desired value
Here's the file in Kotlin
import android.content.Context
import android.util.AttributeSet
import android.widget.ScrollView
class BottomFadeEdgeScrollView : ScrollView {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun getTopFadingEdgeStrength(): Float {
return 0.0f
}
}
The simplest way I found to create a custom view, so I don't have to handle annoying things like to override the onLayout() method, is to make it inherit from a LinearLayout. I also have a LinearLayout at the root of the associated XML file that I inflate, so there is 2 of them at the root.
How can I optimise this, by removing one of this extra LinearLayout, but keep it simple to create custom views ?
MyToolbar.kt:
class MyToolbar #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) :
LinearLayoutCompat(context, attrs, defStyleAttr) {
private val binding = MyToolbarBinding.inflate(LayoutInflater.from(context), this, true)
init {
// [...] Initialization of my view ...
}
}
my_toolbar.xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<!-- Actual content of my view -->
</LinearLayout>
What I have right now after #Pawel and #Cata suggestion. This doesn't work, the LinearLayout use the whole height of the parent, but it should only wrap its content.
MyToolbar.kt:
class MyToolbar #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) :
LinearLayoutCompat(context, attrs, defStyleAttr) {
private val binding
init {
// Tried to add the attributes here as they seems ignored on the `merge` tag
gravity = Gravity.CENTER_VERTICAL
orientation = HORIZONTAL
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
binding = MyToolbarBinding.inflate(LayoutInflater.from(context), this)
// [...] Initialization of the view ...
}
}
my_toolbar.xml:
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<!-- Actual content of the view -->
</merge>
As Pawel suggested you could use merge to do that.
This is a sample from the developer website:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Here you can add your custom content views.. -->
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/add"/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/delete"/>
</merge>
How can I force a MaterialButtonToggleGroup to act like a RadioGroup as in having at least one selected item always? Setting setSingleSelection(true) also adds the possibility to have nothing selected if you click twice on a Button in the group.
Here is my code:
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="#id/BottomSheetDialog_fromFragmentBottomSheetSort_Sort_ToggleButtonGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:singleSelection="true"
app:checkedButton="#+id/BottomSheetDialog_fromFragmentBottomSheetSort_Sort_ToggleButtonGroup_Ascending">
<com.google.android.material.button.MaterialButton
android:id="#id/BottomSheetDialog_fromFragmentBottomSheetSort_Sort_ToggleButtonGroup_Ascending"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/Fragment_BottomSheetDialog_Sort_ToggleButton_Ascending"
app:backgroundTint="#color/custom_button_background_states"
style="#style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="#id/BottomSheetDialog_fromFragmentBottomSheetSort_Sort_ToggleButtonGroup_Descending"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/Fragment_BottomSheetDialog_Sort_ToggleButton_Descending"
app:backgroundTint="#color/custom_button_background_states"
style="#style/Widget.MaterialComponents.Button.OutlinedButton"/>
</com.google.android.material.button.MaterialButtonToggleGroup>
As you can see, even while using app:singleSelection="true" if i click on an already checked button, it unchecks it leaving no button checked in the group.
UPDATE :
app:selectionRequired="true" attribute is available as of version 1.2.0
Override the toggle() method of the MaterialButton class and use it instead of MaterialButton
import android.content.Context
import android.util.AttributeSet
import com.google.android.material.button.MaterialButton
class CustomMaterialToggleButton : MaterialButton {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun toggle() {
if (!isChecked) {
super.toggle()
}
}
}
This will make sure that already checked button is not unchecked on single selection.
As of 1.2.0-alpha03 you can simply use the selectionRequired option:
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="#+id/toggle_button_group"
app:singleSelection="true"
app:selectionRequired="true">
</com.google.android.material.button.MaterialButtonToggleGroup>
Now you can achieve it using the app:selectionRequired attribute.
Something like:
<com.google.android.material.button.MaterialButtonToggleGroup
app:singleSelection="true"
app:selectionRequired="true"
app:checkedButton="#id/..."
..>
Also you can programmatically use the method setSelectionRequired:
buttonGroup.setSelectionRequired(true);
Please note that this attribute requires a minimum of version 1.2.0-alpha03
If you really want to do this, you can go about it this way in Kotlin.
toggle_group.forEach { button ->
button.setOnClickListener { (button as MaterialButton).isChecked = true }
}
This will prevent second click unchecking.