I'm trying to make my list item to be animated after onClick(),Like Facebook messenger. I tried to do this with animateLayoutChanges=true in my list item layout and also my recyclerView's parent layout, but it is not smooth and have some problem, after first click when I want to show hidden fields (setVisibility(VISIBLE)) , it works not bad, but setVisibility(GONE) works not properly, this is what it looks like
And what I'm trying to achieve is this
Any advice how to do that?
this is My custom view
class MessageItemView : RelativeLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet)
private var messageTextView: TextView
private var dateTextView: TextView
private var itemView: View? = null
init {
itemView = LayoutInflater.from(context)?.inflate(R.layout.live_chat_list_item, this)
messageTextView = itemView?.findViewById(R.id.live_chat_msg_text_id) as TextView
dateTextView = itemView?.findViewById(R.id.message_received_date_txt_id) as TextView
}
fun setUPViewModelData(message: MessageModel) {
itemView?.setOnClickListener {
dateTextView.visibility = if (dateTextView.visibility == View.VISIBLE) View.GONE else View.VISIBLE
delivered_status_txt_id.visibility = dateTextView.visibility
}
}
And this is xml of this view
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
<TextView
android:id="#+id/message_received_date_txt_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:visibility="gone" />
<LinearLayout
android:id="#+id/message_wrapper_layout_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/message_received_date_txt_id"
android:orientation="vertical">
<TextView
android:id="#+id/live_chat_msg_text_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<TextView
android:id="#+id/delivered_status_txt_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/message_wrapper_layout_id"
android:visibility="gone" />
</RelativeLayout>
I don't understand what's wrong in my layout hierarchy or what would be good architecture and also interested in Fb's messenger chat item layout hierarchy... Any eideas?
Remove the animateLayoutChanges from your layout and then use the TransitionManager from the support library:
fun setUPViewModelData(message: MessageModel) {
itemView?.setOnClickListener {
TransitionManager.beginDelayedTransition(recyclerView)
dateTextView.visibility = if (dateTextView.visibility == View.VISIBLE) View.GONE else View.VISIBLE
delivered_status_txt_id.visibility = dateTextView.visibility
}
}
Related
Hey I want to show badge drawable. I tried code from here Does BadgeDrawable not work for views within a FrameLayout such as buttons, images, textviews etc.?, BadgeDrawable does not appear on elements other than BottomNavigationView, Badge Drawable not showing. I successfully show but problem is cutting from side edge.
<FrameLayout
android:id="#+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="24dp"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingStart="6dp"
android:paddingTop="4dp"
android:paddingEnd="9dp"
android:paddingBottom="4dp"
app:layout_constraintBottom_toBottomOf="#+id/xyz"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#+id/xyz">
<ImageView
android:id="#+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:src="#drawable/ic_settings"
tools:ignore="ContentDescription" />
</FrameLayout>
activity.kt
fun addBadgeDrawable(count: Int, target: View, parent: FrameLayout, context: Context) {
target.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
val badgeDrawable = BadgeDrawable.create(context)
badgeDrawable.number = count
badgeDrawable.badgeGravity = BadgeDrawable.TOP_END
badgeDrawable.setBoundsFor(target, parent)
parent.foreground = badgeDrawable
target.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})
}
private fun BadgeDrawable.setBoundsFor(#NonNull anchor: View, #NonNull parent: FrameLayout) {
val rect = Rect()
parent.getDrawingRect(rect)
this.bounds = rect
this.updateBadgeCoordinates(anchor, parent)
}
I am setting badge view this code line
addBadgeDrawable(10, binding.icon, binding.container, context)
Output
Expected Output
To avoid cutting, you need to increase paddingTop and paddingEnd in FrameLayout.
Or may be need to add additional ConstraintLayout as a parent for FrameLayout, and play with constraints and paddings.
Hope this will help to someone.
Background
I need to make a dialpad-like View, like on the Phone app.
I'm using a GridLayout of Views. Each cell is of the same size, and contains just a simple TextView that should change its font size if needed.
The problem
I've succeeded, but for some reason it doesn't work well according to the space that it is given.
If it has a lot of space, it works fine:
However, when it gets smaller (example: small screens, landscape, split-window...), only the top buttons of the grid become visible, and they didn't even change their font size, as if they all want to be of the biggest size they can:
What I've tried
I tried to modify various attributes of the views, but none helped.
I know though, that the dial-pad of the Phone app doesn't really change its font size. Up to some size, it gets shown normally, and if it's too small, it changes to a different layout. This is especially important for landscape and split-window modes.
Here's the code I've made (I change the value of "layout_constraintHeight_percent" to check the various sizes for the top area) :
gradle
...
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
...
QueryKeyboard.kt
class QueryKeyboard #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : GridLayout(context, attrs, defStyle) {
init {
orientation = HORIZONTAL
clipChildren = false
clipToPadding = false
columnCount = 3
rowCount = 4
//workaround for a weird issue of seeing just 3 huge buttons, instead of all
val runnable = Runnable {
for (i in 1..9)
addView(generateGridTextButton(i.toString()))
addView(generateGridTextButton("*"))
addView(generateGridTextButton("0"))
addView(generateGridTextButton("+"))
}
if (isInEditMode)
runnable.run()
else
this.doOnPreDraw { runnable.run() }
}
private fun generateGridTextButton(textToShowAndAddUponClick: CharSequence): TextView {
val tv = LayoutInflater.from(context).inflate(R.layout.grid_text_button, this, false) as TextView
tv.text = textToShowAndAddUponClick
return tv
}
}
grid_text_button.xml
<androidx.appcompat.widget.AppCompatTextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:breakStrategy="balanced"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="false"
android:gravity="center"
android:textColor="#000"
android:textSize="36dp"
app:autoSizeMaxTextSize="36dp"
app:autoSizeMinTextSize="12dp"
app:layout_columnWeight="1"
app:layout_gravity="fill"
app:layout_rowWeight="1"
tools:layout_gravity="center"
tools:targetApi="m"
tools:text="1" />
Usage in activity_main.xml :
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity">
<TextView
android:id="#+id/textView" android:layout_width="match_parent" android:layout_height="0px"
android:background="#33ff0000" android:gravity="center" android:text="some content"
app:layout_constraintBottom_toTopOf="#id/queryKeyboard" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.6" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.sample.myapplication.QueryKeyboard
android:id="#+id/queryKeyboard" android:layout_width="match_parent" android:layout_height="0px"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="#id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
EDIT: I tried to wrap the TextView with FrameLayout, to show the size of each cell:
grid_text_button.xml
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content"
app:layout_columnWeight="1" app:layout_gravity="fill" app:layout_rowWeight="1">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless" android:breakStrategy="balanced"
android:clickable="true" android:focusable="true" android:focusableInTouchMode="false" android:gravity="center"
android:textColor="#000" android:textSize="36sp" app:autoSizeMaxTextSize="36sp" app:autoSizeMinTextSize="12sp"
tools:layout_gravity="center" tools:targetApi="m" tools:text="1" />
</FrameLayout>
QueryKeyboard.kt
class QueryKeyboard #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : GridLayout(context, attrs, defStyle) {
private var cellBackgroundColor = 0xffff0000.toInt()
init {
orientation = HORIZONTAL
clipChildren = false
clipToPadding = false
columnCount = 3
rowCount = 4
//workaround for a weird issue of seeing just 3 huge buttons, instead of all
val runnable = Runnable {
for (i in 1..9) {
addView(generateGridTextButton(i.toString()))
}
addView(generateGridTextButton("*"))
addView(generateGridTextButton("0"))
addView(generateGridTextButton("+"))
}
if (isInEditMode)
runnable.run()
else
this.doOnPreDraw { runnable.run() }
}
private fun switchColor() {
cellBackgroundColor = if (cellBackgroundColor == 0xffff0000.toInt()) 0xff00ff00.toInt() else 0xffff0000.toInt()
}
private fun generateGridTextButton(textToShowAndAddUponClick: CharSequence): View {
val view = LayoutInflater.from(context).inflate(R.layout.grid_text_button, this, false)
switchColor()
view.setBackgroundColor(cellBackgroundColor)
view.textView.text = textToShowAndAddUponClick
return view
}
}
And here are the 2 cases, of when it works fine, and when it doesn't:
Same as before. Getting 3 cells, text not centered, and not auto-resizing its font.
The questions
Why don't the cells adjust their sizes, including the font size of each of them? How come I see just 3 cells when it's too small? How can I fix it?
Is there a better alternative? I guess I could use LinearLayout of multiple LinearLayout instances, but that's just weird for this case... After all, how often do you use GridLayout... :)
How can I detect when it's just too small, so that I switch to another layout, like on the Phone app, including all the various cases they used (even split-window)? Is it possible they just used qualifier for the layouts? If so, which is recommended for this case ?
Why don't the cells adjust their sizes, including the font size of each of them? How can I fix it?
A cell can dynamically adjust its size based on two main constraints: the weight of its parent and the value of its textsize or childview. Having a fixed textsize on cells can lead to inconsistency in design. To fix this issue you can either set a layout weight on parentview with cells width and height matching parents or create different dimensions of textsize for targeted devices.
Is there a better alternative? I guess I could use LinearLayout of multiple LinearLayout instances, but that's just weird for this case... After all, how often do you use GridLayout... :)
There is a better way and that is what you're currently implementing The GridLayout. Using indented LinearLayouts would limit you from lots of benefits and would make you write more code, take for example cases where you need to switch or animate cells, access the nth column of nth row, dynamically change cell span, etc. All these can be done via Gridlayout with a few lines of code. It is more powerful than you think.
How can I detect when it's just too small, so that I switch to another layout, like on the Phone app, including all the various cases they used (even split-window)? Is it possible they just used qualifier for the layouts? If so, which is recommended for this case ?
There's nothing for you to detect, just follow the guidelines and Android would do the detecting.
Here are a couple of ways you can manage your scenario based on Android Design guidelines
First:
Create a layout landscape and portrait mode for your activity (layout/activity.xml and layout-land/activity.xml)
layout/activity.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="2"
tools:context=".MainActivity">
<TextView
android:id="#+id/textView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#33ff0000"
android:gravity="center" android:text="some content"
android:layout_weight="1" />
<com.sample.myapplication.QueryKeyboard
android:id="#+id/queryKeyboard"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
layout-land/activity.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:weightSum="2"
tools:context=".MainActivity">
<TextView
android:id="#+id/textView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="#33ff0000"
android:gravity="center" android:text="some content"
android:layout_weight="1" />
<com.sample.myapplication.QueryKeyboard
android:id="#+id/queryKeyboard"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
Furthermore You need to handle textsize for various screen size.
This textsize calculation can be handled automatically by adding Intuit android library to your dependency list in gradle
dependencies {
implementation 'com.intuit.ssp:ssp-android:1.0.6'
}
Then in your grid button textsize call
android:textSize="#dimens/_30ssp"
OK I've changed the layout files a bit, to avoid the 3-cells issue. It still occurs, but on much smaller sizes. Sadly the font sizes issue remains the same, and even the very small this time.
If anyone finds out why this occurs, please let me know. For now I consider this as a bug, so I've reported here.
grid_text_button.xml
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true" android:focusable="true" android:focusableInTouchMode="false" app:layout_columnWeight="1"
app:layout_gravity="fill" app:layout_rowWeight="1" tools:layout_gravity="center">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:breakStrategy="balanced" android:gravity="center"
android:textColor="#000" app:autoSizeMaxTextSize="36sp" app:autoSizeMinTextSize="12sp" tools:targetApi="m"
tools:text="1" />
</FrameLayout>
QueryKeyboard.kt
class QueryKeyboard #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : GridLayout(context, attrs, defStyle) {
// private var cellBackgroundColor = 0xffff0000.toInt()
init {
orientation = HORIZONTAL
clipChildren = false
clipToPadding = false
columnCount = 3
rowCount = 4
for (i in 1..9)
addView(generateGridTextButton(i.toString()))
addView(generateGridTextButton("*"))
addView(generateGridTextButton("0"))
addView(generateGridTextButton("+"))
}
// private fun switchColor() {
// cellBackgroundColor = if (cellBackgroundColor == 0xffff0000.toInt()) 0xff00ff00.toInt() else 0xffff0000.toInt()
// }
private fun generateGridTextButton(textToShowAndAddUponClick: CharSequence): View {
val view = LayoutInflater.from(context).inflate(R.layout.grid_text_button, this, false)
// switchColor()
// view.setBackgroundColor(cellBackgroundColor)
view.textView.text = textToShowAndAddUponClick
return view
}
}
An alternative to the GridLayout implementation, that doesn't have this issue, but it's still weird that I would use it, is as I wrote, a LinearLayout of LinearLayouts :
class QueryKeyboard2 #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : LinearLayout(context, attrs, defStyleAttr) {
//private var cellBackgroundColor = 0xffff0000.toInt()
init {
orientation = VERTICAL
clipChildren = false
clipToPadding = false
val columnCount = 3
val rowCount = 4
val cellsList = ArrayList<View>()
for (i in 1..9)
cellsList.add(generateGridTextButton(i.toString()))
cellsList.add(generateGridTextButton("*"))
cellsList.add(generateGridTextButton("0"))
cellsList.add(generateGridTextButton("+"))
for (i in 0 until rowCount) {
val rowLayout = generateRowLayout(context)
for (j in 0 until columnCount) {
val cellView = cellsList[i * columnCount + j]
val cellLayoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT)
cellLayoutParams.weight = 1f
rowLayout.addView(cellView, cellLayoutParams)
}
// switchColor()
// rowLayout.setBackgroundColor(cellBackgroundColor)
addView(rowLayout)
}
}
private fun generateRowLayout(context: Context): LinearLayout {
val result = LinearLayout(context)
result.layoutParams = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0)
(result.layoutParams as LayoutParams).weight = 1f
result.orientation = HORIZONTAL
return result
}
//private fun switchColor() {
// cellBackgroundColor = if (cellBackgroundColor == 0xffff0000.toInt()) 0xff00ff00.toInt() else 0xffff0000.toInt()
//}
private fun generateGridTextButton(textToShowAndAddUponClick: CharSequence): View {
val view = LayoutInflater.from(context).inflate(R.layout.grid_text_button, this, false)
//switchColor()
//view.setBackgroundColor(cellBackgroundColor)
view.textView.text = textToShowAndAddUponClick
return view
}
}
As for trying to make it shown only when there is enough space (height) , I've set the layout to use it on "res/layout-h400dp" (can be changed according to the needs), and a different one, where the dialpad is on the right, for the normal "res/layout".
I have a Custom view:
class CustomerView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
private var txtName: TextView
private var txtAge: TextView
init {
View.inflate(context, R.layout.view_customer, null)
txtName = findViewById(R.id.txtTestName)
txtAge = findViewById(R.id.txtTestAge)
txtName.text = "Person"
txtAge.text = "61"
}
}
My Layout view_customer looks like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/txtTestName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="name" />
<TextView
android:id="#+id/txtTestAge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="age" />
</LinearLayout>
however when I call it in my other page, the app crashes saying
Caused by: java.lang.IllegalStateException:
findViewById(R.id.txtTestName) must not be null
at com.helcim.helcimcommercemobile.customviews.CustomerView.(CustomerView.kt:19)
which is when I am trying to assign txtName
I don't really understand how it is null when I have it in the layout view. and it is named the same.
Am I creating a custom view incorrectly?
You have to add parent layout as parameter to 'inflate' method.
class CustomerView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
private var txtName: TextView
private var txtAge: TextView
init {
View.inflate(context, R.layout.view_customer, this)
txtName = findViewById(R.id.txtTestName)
txtAge = findViewById(R.id.txtTestAge)
txtName.text = "Derek"
txtAge.text = "23"
}
}
Lets say I have a simple Layout with a MvxListView:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res/LiivControl.Client.Droid"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Mvx.MvxListView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
local:MvxBind="ItemsSource AutoListItems; ItemClick AutoListItemClicked"
local:MvxItemTemplate="#layout/vbmvxautoviewlistitem" />
</LinearLayout>
My item template layout is as follows:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res/LiivControl.Client.Droid"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="10dip"
android:paddingBottom="10dip"
android:paddingLeft="15dip">
<TextView
android:id="#+id/list_complex_title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="#+id/list_complex_caption"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
I would like to specify the bindings for the two textview elements in my itemtemplate from code behind. I am not sure how best to go about it. I'm guess I could do something "OnViewModelSet" in the view code behind of the MvxListView. I have tried the following but for it obviously doesn't work because it can't find the control.
protected override void OnViewModelSet()
{
IVbMvxAutoListViewModel vm = base.ViewModel as IVbMvxAutoListViewModel;
TextView title = this.FindViewById<TextView>(Resource.Id.list_complex_title);
this.CreateBinding(title).For(x => x.Text).To(vm.ListItemDescriptor.TitlePropName).Apply();
TextView subTitle = this.FindViewById<TextView>(Resource.Id.list_complex_caption);
this.CreateBinding(subTitle).For(x => x.Text).To(vm.ListItemDescriptor.SubTitlePropName).Apply();
base.OnViewModelSet();
}
My other thought was to somehow intercept the oncreate for the itemtemplate view but OnCreate doesn't get called if I create a view code file for my itemtemplate layout.
To do the bindings in code it's probably best to:
implement a custom MvxListViewItem
implement a custom MvxAdapter to return the custom list view item
implement a custom MvxListView to use the custom MvxAdapter
Not tested, but the code for this is roughly:
1. implement a custom MvxListViewItem
public class CustomListItemView
: MvxListItemView
{
public MvxListItemView(Context context,
IMvxLayoutInflater layoutInflater,
object dataContext,
int templateId)
: base(context, layoutInflater, dataContext, templateId)
{
var control = this.FindViewById<TextView>(Resource.Id.list_complex_title);
var set = this.CreateBindingSet<CustomListViewItem, YourThing>();
set.Bind(control).To(vm => vm.Title);
set.Apply();
}
}
2. Create a custom MvxAdapter
In this override CreateBindableView
public class CustomAdapter
: MvxAdapter
{
public CustomAdapter(Context context)
: base(context)
{
}
protected override IMvxListItemView CreateBindableView(object dataContext, int templateId)
{
return new CustomListItemView(_context, _bindingContext.LayoutInflater, dataContext, templateId);
}
}
original: https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross.Binding.Droid/Views/MvxAdapter.cs#L298
3. implement a custom MvxListView to use the adapter
This should be as simple as:
public class CustomListView
: MvxListView
{
public CustomListView(Context context, IAttributeSet attrs)
: base(context, attrs, new CustomAdapter(context))
{
}
}
As long as this is in your main UI assembly, this should be useable in your axml as:
<CustomListView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
local:MvxBind="ItemsSource AutoListItems; ItemClick AutoListItemClicked"
local:MvxItemTemplate="#layout/vbmvxautoviewlistitem" />
If CustomListView is not in your main UI assembly, then there are some tricks to get MvvmCross to pick it up during your Setup - see Providing Custom Android View Assemblies in https://github.com/MvvmCross/MvvmCross/wiki/Customising-using-App-and-Setup#wiki-providing-custom-views-android
The above is the best way to do this (IMO) - but if you wanted to, then you could do it in less code by just applying the bindings inside the custom adapter and by setting that adapter in OnCreate in your Activity
I am trying to create a custom View that would replace a certain layout that I use at multiple places, but I am struggling to do so.
Basically, I want to replace this:
<RelativeLayout
android:id="#+id/dolphinLine"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="#drawable/background_box_light_blue"
android:padding="10dip"
android:layout_margin="10dip">
<TextView
android:id="#+id/dolphinTitle"
android:layout_width="200dip"
android:layout_height="100dip"
android:layout_alignParentLeft="true"
android:layout_marginLeft="10dip"
android:text="#string/my_title"
android:textSize="30dip"
android:textStyle="bold"
android:textColor="#2E4C71"
android:gravity="center"/>
<Button
android:id="#+id/dolphinMinusButton"
android:layout_width="100dip"
android:layout_height="100dip"
android:layout_toRightOf="#+id/dolphinTitle"
android:layout_marginLeft="30dip"
android:text="#string/minus_button"
android:textSize="70dip"
android:textStyle="bold"
android:gravity="center"
android:layout_marginTop="1dip"
android:background="#drawable/button_blue_square_selector"
android:textColor="#FFFFFF"
android:onClick="onClick"/>
<TextView
android:id="#+id/dolphinValue"
android:layout_width="100dip"
android:layout_height="100dip"
android:layout_marginLeft="15dip"
android:background="#android:drawable/editbox_background"
android:layout_toRightOf="#+id/dolphinMinusButton"
android:text="0"
android:textColor="#2E4C71"
android:textSize="50dip"
android:gravity="center"
android:textStyle="bold"
android:inputType="none"/>
<Button
android:id="#+id/dolphinPlusButton"
android:layout_width="100dip"
android:layout_height="100dip"
android:layout_toRightOf="#+id/dolphinValue"
android:layout_marginLeft="15dip"
android:text="#string/plus_button"
android:textSize="70dip"
android:textStyle="bold"
android:gravity="center"
android:layout_marginTop="1dip"
android:background="#drawable/button_blue_square_selector"
android:textColor="#FFFFFF"
android:onClick="onClick"/>
</RelativeLayout>
By this:
<view class="com.example.MyQuantityBox"
android:id="#+id/dolphinBox"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:myCustomAttribute="#string/my_title"/>
So, I do not want a custom layout, I want a custom View (it should not be possible for this view to have child).
The only thing that could change from one instance of a MyQuantityBox to another is the title. I would very much like to be able to specify this in the XML (as I do on the last XML line)
How can I do this? Should I put the RelativeLayout in a XML file in /res/layout and inflate it in my MyBoxQuantity class? If yes how do I do so?
Thanks!
A bit old, but I thought sharing how I'd do it, based on chubbsondubs' answer:
I use FrameLayout (see Documentation), since it is used to contain a single view, and inflate into it the view from the xml.
Code following:
public class MyView extends FrameLayout {
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public MyView(Context context) {
super(context);
initView();
}
private void initView() {
inflate(getContext(), R.layout.my_view_layout, this);
}
}
Here is a simple demo to create customview (compoundview) by inflating from xml
attrs.xml
<resources>
<declare-styleable name="CustomView">
<attr format="string" name="text"/>
<attr format="reference" name="image"/>
</declare-styleable>
</resources>
CustomView.kt
class CustomView #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
ConstraintLayout(context, attrs, defStyleAttr) {
init {
init(attrs)
}
private fun init(attrs: AttributeSet?) {
View.inflate(context, R.layout.custom_layout, this)
val image_thumb = findViewById<ImageView>(R.id.image_thumb)
val text_title = findViewById<TextView>(R.id.text_title)
val ta = context.obtainStyledAttributes(attrs, R.styleable.CustomView)
try {
val text = ta.getString(R.styleable.CustomView_text)
val drawableId = ta.getResourceId(R.styleable.CustomView_image, 0)
if (drawableId != 0) {
val drawable = AppCompatResources.getDrawable(context, drawableId)
image_thumb.setImageDrawable(drawable)
}
text_title.text = text
} finally {
ta.recycle()
}
}
}
custom_layout.xml
We should use merge here instead of ConstraintLayout because
If we use ConstraintLayout here, layout hierarchy will be ConstraintLayout->ConstraintLayout -> ImageView + TextView => we have 1 redundant ConstraintLayout => not very good for performance
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:parentTag="android.support.constraint.ConstraintLayout">
<ImageView
android:id="#+id/image_thumb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="ContentDescription"
tools:src="#mipmap/ic_launcher" />
<TextView
android:id="#+id/text_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="#id/image_thumb"
app:layout_constraintStart_toStartOf="#id/image_thumb"
app:layout_constraintTop_toBottomOf="#id/image_thumb"
tools:text="Text" />
</merge>
Using
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<your_package.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#f00"
app:image="#drawable/ic_android"
app:text="Android" />
<your_package.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#0f0"
app:image="#drawable/ic_adb"
app:text="ADB" />
</LinearLayout>
Result
See full code on:
Github
Yes you can do this. RelativeLayout, LinearLayout, etc are Views so a custom layout is a custom view. Just something to consider because if you wanted to create a custom layout you could.
What you want to do is create a Compound Control. You'll create a subclass of RelativeLayout, add all our your components in code (TextView, etc), and in your constructor you can read the attributes passed in from the XML. You can then pass that attribute to your title TextView.
http://developer.android.com/guide/topics/ui/custom-components.html
Use the LayoutInflater as I shown below.
public View myView() {
View v; // Creating an instance for View Object
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(R.layout.myview, null);
TextView text1 = v.findViewById(R.id.dolphinTitle);
Button btn1 = v.findViewById(R.id.dolphinMinusButton);
TextView text2 = v.findViewById(R.id.dolphinValue);
Button btn2 = v.findViewById(R.id.dolphinPlusButton);
return v;
}
In practice, I have found that you need to be a bit careful, especially if you are using a bit of xml repeatedly. Suppose, for example, that you have a table that you wish to create a table row for each entry in a list. You've set up some xml:
In my_table_row.xml:
<?xml version="1.0" encoding="utf-8"?>
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" android:id="#+id/myTableRow">
<ImageButton android:src="#android:drawable/ic_menu_delete" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="#+id/rowButton"/>
<TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="TextView" android:id="#+id/rowText"></TextView>
</TableRow>
Then you want to create it once per row with some code. It assume that you have defined a parent TableLayout myTable to attach the Rows to.
for (int i=0; i<numRows; i++) {
/*
* 1. Make the row and attach it to myTable. For some reason this doesn't seem
* to return the TableRow as you might expect from the xml, so you need to
* receive the View it returns and then find the TableRow and other items, as
* per step 2.
*/
LayoutInflater inflater = (LayoutInflater)getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.my_table_row, myTable, true);
// 2. Get all the things that we need to refer to to alter in any way.
TableRow tr = (TableRow) v.findViewById(R.id.profileTableRow);
ImageButton rowButton = (ImageButton) v.findViewById(R.id.rowButton);
TextView rowText = (TextView) v.findViewById(R.id.rowText);
// 3. Configure them out as you need to
rowText.setText("Text for this row");
rowButton.setId(i); // So that when it is clicked we know which one has been clicked!
rowButton.setOnClickListener(this); // See note below ...
/*
* To ensure that when finding views by id on the next time round this
* loop (or later) gie lots of spurious, unique, ids.
*/
rowText.setId(1000+i);
tr.setId(3000+i);
}
For a clear simple example on handling rowButton.setOnClickListener(this), see Onclicklistener for a programmatically created button.
There are multiple answers which point the same way in different approaches, I believe the below is the simplest approach without using any third-party libraries, even you can use it using Java.
In Kotlin;
Create values/attr.xml
<resources>
<declare-styleable name="DetailsView">
<attr format="string" name="text"/>
<attr format="string" name="value"/>
</declare-styleable>
</resources>
Create layout/details_view.xml file for your view
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/text_label"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="0.5"
tools:text="Label" />
<TextView
android:id="#+id/text_value"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="0.5"
tools:text="Value" />
</LinearLayout>
</merge>
Create the custom view widget DetailsView.kt
import android.content.Context
import android.content.res.TypedArray
import android.util.AttributeSet
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import com.payable.taponphone.R
class DetailsView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
private val attributes: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.DetailsView)
private val view: View = View.inflate(context, R.layout.details_view, this)
init {
view.findViewById<TextView>(R.id.text_label).text = attributes.getString(R.styleable.DetailsView_text)
view.findViewById<TextView>(R.id.text_value).text = attributes.getString(R.styleable.DetailsView_value)
}
}
That's it now you can call the widget anywhere in your app as below
<com.yourapp.widget.DetailsView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:text="Welcome"
app:value="Feb" />
A simple Custom View using Kotlin
Replace FrameLayout with whatever view you Like to extend
/**
* Simple Custom view
*/
class CustomView#JvmOverloads
constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: FrameLayout(context, attrs, defStyleAttr) {
init {
// Init View
val rootView = (getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater)
.inflate(R.layout.custom_layout, this, true)
val titleView= rootView.findViewById(id.txtTitle)
// Load Values from XML
val attrsArray = getContext().obtainStyledAttributes(attrs, R.styleable.CutsomView, defStyleAttr, 0)
val titleString = attrsArray.getString(R.styleable.cutomAttrsText)
attrsArray.recycle()
}
}