How to fill item width children in reyclerview android - android

Hey I am working in android. I want to fit children in whole view of recyclerview android. I have horizontal recylerview. I will show what I want in diagram.
Scenario 1
when I have more item in recyclerview, I want to show children like this in my recycler view.
Expected Output
Scenario 2
when I have three item I want to show like this. It will fill whole view in reyclerview.
Expected Output
Scenario 3
When I have Two item in reyclerview, I need to look like this
Expected Output
The problem I am getting that I have 3 item, the view is not filling fully. Actually I want to stretch whole view to full width like Scenario 2.
Actual output
item_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:orientation="vertical">
<LinearLayout
android:id="#+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="#drawable/drawable_selector"
android:orientation="vertical">
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp" />
<TextView
android:id="#+id/subText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp" />
</LinearLayout>
<RelativeLayout
android:id="#+id/tagContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:visibility="gone">
<TextView
android:id="#+id/tagText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingStart="10dp"
android:paddingEnd="10dp" />
</RelativeLayout>
</FrameLayout>
UPDATE
after #Cheticamp suggestion I did this code
companion object {
fun bindView(parent: ViewGroup): XYZViewHolder {
val view = XyzLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
val lp = view.container.layoutParams
lp.width = parent.measuredWidth / 3
return OptionsViewHolder(
view
)
}
}
As you can see my last item is cut from the end.
I think in my framelayout i Used marginEnd 10 dp is it causing issue? please refer my layout if you need more. And one more thing I didn't divide framelayout instead I take linear layout as container. I am adding my github link.

You can change the width of the RecyclerView item views in onCreateViewHolder(). The ViewGroup parent is the RecyclerView.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_view, parent, false)
val lp = view.layoutParams
// Change the width of the item view here
lp.width = parent.measuredWidth / 3 // width is 1/3 of the width of the RecyclerView
return ItemViewHolder(view)
}

Customize frameLayout class
make a class inherit FrameLayout or another layout you use.
public class SquareFrameLayout extends FrameLayout {
public SquareFrameLayout(#NonNull Context context) {
super(context);
}
public SquareFrameLayout(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public SquareFrameLayout(#NonNull Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public SquareFrameLayout(#NonNull Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
}
onMeasure method is important.
then change root layout item recyclerView to SquareFrameLayout (The class built now)
like this:
<com.example.app.SquareFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/background_rv_item"
android:orientation="vertical"
android:padding="16dp"
android:layout_margin="4dp"
xmlns:tools="http://schemas.android.com/tools">
//your items
</com.example.app.SquareFrameLayout>

You can use flexbox-layout library provided by Google.
You can adjust the items according to your need.
Download the app and see this demo working for you or not. https://github.com/google/flexbox-layout

Related

Reuse ViewGroup

I use this kind of ViewGroup:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/icon"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="#drawable/icon1"/>
<TextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/text1"/>
<TextView
android:id="#+id/data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
I must use 2 such layouts In my fragment, but with different icon and title. Is there some way to implement it without copy/paste and RecyclerView?
There are several ways to deal with it.
1. Use the include tag.
1.1. Move LinearLayout to a separate file.
1.2 Add layout using the include tag two times with different ids:
<LinearLayout ...>
<include layout="#layout/your_layout" android:id="#+id/first" />
<include layout="#layout/your_layout" android:id="#+id/second" />
</LinearLayout>
1.3 Set content programmatically:
View first = findViewById(R.id.first);
first.findViewById(R.id.date).setText("05/05/2020");
View second = findViewById(R.id.second);
second.findViewById(R.id.date).setText("04/04/2020");
2. Implement a custom view.
There are two ways also. The first is to inflate layout inside FrameLayout. The second is to extend LinearLayout and add content programmatically. I'll show you the first one.
public class YourCustomView extends FrameLayout {
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
inflate(context, R.layout.your_custom_view_layout, this);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyView(Context context) {
this(context, null);
}
public void setContent(int iconRes, int titleRes, String data) {
findViewById(R.id.icon).setDrawableRes(iconRes);
findViewById(R.id.title).setDrawableRes(titleRes);
findViewById(R.id.data).setText(data);
}
}
3. Just copy-paste it :)
As I see icon and title are static and only data content changes, so it is not worth it to reuse such a simple layout, IMO.

Custom LinearLayout - childs not visible

I build customView extending LinearLayout and having simple 3 childrens(2 TextView and ImageView). I create this view dynamically in code and adding it to parent LinearLayout. This view has background, so I can easily spot on the screen, that it is inflated correctly in its place, but any of child is not visible. I checked LayoutInspector and it shows that everything is setted correctly(text values to TextViews and picture to ImageView), but somehow when I try to locate them on inspector they are shown as little dot over my customView:
My CustomView is called DayTileView and this is square with gray background. As you can see on inspector on the left childrens are filled with content. Layout of View:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<merge>
<TextView
android:id="#+id/day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textColor="#android:color/white"
/>
<TextView
android:id="#+id/dayName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textColor="#android:color/white"
/>
<ImageView
android:id="#+id/padlock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="#drawable/ic_padlock"
/>
</merge>
</layout>
And its code:
public class DayTileView extends LinearLayout {
private DayTileBinding mBinding;
public DayTileView(Context context) {
super(context);
init();
}
public DayTileView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public DayTileView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mBinding = DataBindingUtil.inflate(LayoutInflater.from(getContext()), R.layout.day_tile, this, true);
setOrientation(VERTICAL);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
setMeasuredDimension(width, width);
}
public void setDay(int day, int month, int year) {
DateTime settedDay = new DateTime().withYear(year).withMonthOfYear(month).withDayOfMonth(day);
mBinding.day.setText(String.valueOf(day));
String dayName = settedDay.dayOfWeek().getAsText();
mBinding.dayName.setText(dayName);
boolean isWeekend = settedDay.dayOfWeek().get() == 6 || settedDay.dayOfWeek().get() == 7;
setBackgroundColor(ContextCompat.getColor(getContext(), isWeekend ? R.color.weekend_bg : R.color.weekday_bg));
}
}
Its use in another CustomView which is also LinearLayout but wiht horizontal orientation (PlannedDayView on inspector):
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<merge>
<*.customViews.DayTileView
android:id="#+id/dayTile"
android:layout_width="0dp"
android:layout_weight="0.2"
android:layout_height="wrap_content"/>
<LinearLayout
android:id="#+id/container"
android:orientation="vertical"
android:layout_weight="1.2"
android:layout_width="0dp"
android:layout_height="wrap_content"/>
</merge>
</layout>
Has anyone any idea what could be casuing this (childs out of view)? When I replace merge for LinearLayout with vertical orientation and same background everything in Design mode of layout is visible correctly, so it should work.
EDIT:
I found out, that if I set during View initalization Padding Top to 10px then dot is moving down. So it looks like from some reasons Android didn't made to inflate correctly TextViews and ImageView
I found out what was the problem:
I overrided onMeasure and didn't measure child Views. Earlier I was using such code to make square View not square ViewGroup.
Corrected code:
final int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
setMeasuredDimension(width, width);
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY));
After setting correct width and height for View I must measure whole view with new MeasureSpec

Make width of horizontal recycler view items 70% of screen width

I am trying to create this effect
I am using a recycler view but my issue, is that each card is 100% of the width of the screen as apposed to 70%.
Here is the xml code for each item
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rowLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/scale_20dp">
<LinearLayout
android:id="#+id/button_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<android.support.v7.widget.CardView
android:id="#+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="#+id/currentYear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#drawable/paymentscreengrey"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="35dp"
android:paddingTop="#dimen/scale_50dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/scale_20dp"
android:text="**** **** **** 5432"
android:textColor="#color/white"
android:textSize="#dimen/scale_20dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="2345"
android:textColor="#color/white"
android:textSize="#dimen/scale_16dp" />
If you want the first item to be left-aligned (i.e., reproduce what's in the screenshot), subclass LinearLayoutManager and override the three generate*LayoutParams methods. Here's how I did it: https://gist.github.com/bolot/6f1838d29d5b8a87b5fcadbeb53fb6f0.
class PeekingLinearLayoutManager : LinearLayoutManager {
#JvmOverloads
constructor(context: Context?, #RecyclerView.Orientation orientation: Int = RecyclerView.VERTICAL, reverseLayout: Boolean = false) : super(context, orientation, reverseLayout)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
override fun generateDefaultLayoutParams() =
scaledLayoutParams(super.generateDefaultLayoutParams())
override fun generateLayoutParams(lp: ViewGroup.LayoutParams?) =
scaledLayoutParams(super.generateLayoutParams(lp))
override fun generateLayoutParams(c: Context?, attrs: AttributeSet?) =
scaledLayoutParams(super.generateLayoutParams(c, attrs))
private fun scaledLayoutParams(layoutParams: RecyclerView.LayoutParams) =
layoutParams.apply {
when(orientation) {
HORIZONTAL -> width = (horizontalSpace * ratio).toInt()
VERTICAL -> height = (verticalSpace * ratio).toInt()
}
}
private val horizontalSpace get() = width - paddingStart - paddingEnd
private val verticalSpace get() = height - paddingTop - paddingBottom
private val ratio = 0.9f // change to 0.7f for 70%
}
This solution is based on/inspired by the spanning (i.e., fit all items to screen) linear layout manager https://gist.github.com/heinrichreimer/39f9d2f9023a184d96f8.
Btw, if you just want to show the items to the left and to the right, while the current item is centered, you can add padding to the recycler view and set clipToPadding to false. In this case you don't even need a custom layout manager.
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager">
I needed to have the items in my horizontal RecyclerView to be 70% of the width of the recyclerview. It can be easily done in onCreateViewHolder():
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.row, parent, false);
view.layoutParams = ViewGroup.LayoutParams((parent.width * 0.7).toInt(),ViewGroup.LayoutParams.MATCH_PARENT)
return ViewHolder(view);
}
Two ways of doing this really.
1)Use a custom view for your the recycled view. Override onMeasure to return its width as 70 percent of the screen.
2)In your Recycler View adapter, when you create the view set its width to be 70 percent of the screen's width.
In either case you get the screen size from the Display and just multiply the width by .7. In the first case you set that as the EXACT measured width, in the second you set it in the layout param. The second is probably a bit easier.
I had a similar issue where I had a fragment with a horizontal RecyclerView and wanted each view item's width to be a third of the user's screen. Solved it by adding the following in the constructor of our ViewHolder class (in my case I wanted each view holder to be 1/3 of the screen instead of 70%):
private class OurViewHolder extends RecyclerView.ViewHolder {
...
public OurViewHolder (LayoutInflater inflater, ViewGroup parent) {
super (inflater.inflate (R.layout.list_item_layout, parent, false));
// This code is used to get the screen dimensions of the user's device
DisplayMetrics displayMetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
// Set the ViewHolder width to be a third of the screen size, and height to wrap content
itemView.setLayoutParams(new RecyclerView.LayoutParams(width/3, RecyclerView.LayoutParams.WRAP_CONTENT));
...
}
...
for your row.xml parent, you must use wrap_content for its width then add this property.
android:paddingLeft="25dp"
You will get same result
Just updated a bit from this answer by adding a margin and getting displayMetrics from the resources to make it clean:
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(product: StyleMaster) = with(itemView) {
val width = context.resources.displayMetrics?.widthPixels
if (width != null) {
val params = RecyclerView.LayoutParams(width / 2, RecyclerView.LayoutParams.WRAP_CONTENT)
params.setMargins(2, 2, 2, 2)
itemView.layoutParams = params
}
// Rest of the code goes here...
Try changing LinearLayout with PercentRelativeLayout to "wrap" the RecyclerView and then set the its width to 70%.
Change this:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/rv"
/>
</LinearLayout>
With this:
<android.support.percent.PercentRelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_widthPercent="70%"
android:id="#+id/rv"
/>
EDIT
Since Percent Support Library comes along with Android Support Library 23 so please make sure that you update Android Support Library in SDK Manager to the latest version already (actually 24). And then add a dependency like below in build.gradle file:
compile 'com.android.support:percent:24.0.0'

Why is Android Studio designer displaying my custom view nested inside itself, while it isn't

I've got a custom view for my app named AvatarView:
<?xml version="1.0" encoding="utf-8"?>
<com.ulouder.views.AdvancedRelativeLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_gravity="center_horizontal"
android:layout_margin="0dp"
android:padding="0dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CP"
android:id="#+id/initialsView"
android:layout_alignTop="#+id/avatarView"
android:layout_alignLeft="#+id/avatarView"
android:layout_alignBottom="#+id/avatarView"
android:layout_alignRight="#+id/avatarView"
android:background="#drawable/avatar_background"
android:textColor="#color/white"
android:gravity="center"
android:textStyle="bold"
android:textSize="8sp" />
<com.makeramen.roundedimageview.RoundedImageView
app:riv_corner_radius="20dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/avatarView"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="4dp"
android:layout_marginLeft="4dp"
android:layout_marginBottom="4dp"
app:riv_border_color="#color/lightGray"
app:riv_border_width="0.2dp" />
</com.uLouder.views.AdvancedRelativeLayout>
AdvancedRelativeLayout is just a superclass of RelativeLayout with a small fix, nothing special there. Then, I've created a view that uses my custom view:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<com.ulouder.views.AvatarView
android:layout_width="50dp"
android:layout_height="50dp"/>
</LinearLayout>
Nothing fancy either. But in the designer view of the second layout XML, I'm getting this:
The editor displays my view hierarchy like it has a nested instance of itself, while clearly there isn't. If I delete either one, they both get deleted. If I declare attributes on one of them, other also gets it. They are clearly the same instance. The only exception is setting an ID. Then the problem disappears, and only single instance is displayed as expected.
I've rebuilt the project, restarted Android Studio, but it's still the same. What am I doing wrong?
UPDATE: Nope, now, after editing id, the problem still continues again.
UPDATE 2: It's not just a layout so I can't use <include> tag. It's a custom view which has custom logic inside.
UPDATE 3: Here is my custom view's (relevant) code:
public class AvatarView extends FrameLayout {
public AvatarView(Context context) {
super(context);
init();
}
TextView initialsView;
RoundedImageView imageView;
public AvatarView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
void init(){
inflate(getContext(), R.layout.view_avatar, this);
initialsView = (TextView) findViewById(R.id.innerInitialsView);
imageView = (RoundedImageView) findViewById(R.id.innerImageView);
}
#SuppressWarnings("SuspiciousNameCombination")
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec); //always square
imageView.setCornerRadius(widthMeasureSpec / 2f);
initialsView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, widthMeasureSpec * 30f);
}
}
UPDATE 4: It appears that this happens wherever I put my custom AvatarView class, not just at one place.
I did not find any reason to inflate the same view inside your class constructor method after checking the custom views documentation. Try to remove the inflate inside your init method.
...
public AvatarView(Context context) {
super(context);
init();
}
...
public AvatarView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
void init(){
// inflate(getContext(), R.layout.view_avatar, this);
initialsView = (TextView) findViewById(R.id.innerInitialsView);
imageView = (RoundedImageView) findViewById(R.id.innerImageView);
}
...

Create a custom View by inflating a layout?

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()
}
}

Categories

Resources