is it possible to access programmatically a layout which is set to a Preference?
Here is what I have, a very simple project - proof of concept
The Preference Activity:
package com.example;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.util.Log;
import android.view.View;
public class PreferenceExampleActivity extends PreferenceActivity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
ImageView v = (ImageView) findViewById(R.id.iconka);
}
}
The resource XML:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="settings">
<PreferenceCategory
android:title="Category Setting Name"
android:order="1"
android:key="Main">
<Preference
android:order="1"
android:title="Setting"
android:summary="Setting1"
android:layout="#layout/profile_preference_row"
android:key="profile" />
</PreferenceCategory>
</PreferenceScreen>
The custom layout for the Preference:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/widget_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:paddingRight="?android:attr/scrollbarSize">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dip"
android:layout_marginRight="6dip"
android:layout_marginTop="6dip"
android:layout_marginBottom="6dip"
android:layout_weight="1">
<TextView android:id="#+android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
<TextView android:id="#+android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#android:id/title"
android:layout_alignLeft="#android:id/title"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="4" />
</RelativeLayout>
<ImageView
android:id="#+id/iconka"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
</LinearLayout>
What I want is to be able to access the "iconka" ImageView from the Activity and change the image from there. I am using API 8 (Android 2.2)
Currently the "v" is null and I don't have any idea why is that.
A hint will be much appreciated!
Update - The Solution:
Actually, I needed a custom preference which I can modify for my needs. This one is a practical guide how to create your own Custom preference in your project:
Android & Amir - Android Preferences
See the part when the author creates a custom preference class.
After 20 min of hair pulling I found an elegant solution to this problem. First, extend the preference, then override the getView(View convertView, ViewGroup parent) method. My case was this: I had a preference layout with app icon and 2 text views(app name and version). I want to change app version programatically. How do I do this? just take a look below:
public class AboutUsPreference extends Preference {
public AboutUsPreference(Context context) {
super(context);
}
public AboutUsPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AboutUsPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public View getView(View convertView, ViewGroup parent) {
View v = super.getView(convertView, parent);
((TextView)v.findViewById(R.id.textView2)).setText(getAppVersion());
return v;
}
private String getAppVersion(){
PackageInfo pInfo = null;
try {
pInfo = getContext().getPackageManager().getPackageInfo(getContext().getPackageName(), 0);
} catch (NameNotFoundException e) {
Log.e(getClass().getName(), e.getMessage(), e);
return "";
}
String version = pInfo.versionName;
return getContext().getString(R.string.version, version);
}
}
The solution is this: View v = super.getView(convertView, parent); when overriding getView method. super.getview call will return your layout view.
And my xml preference looks like:
<com.audioRec.android.settings.aboutUs.AboutUsPreference
android:layout="#layout/about_preference_layout"/>
Try taking a look at the following discussions on imageview. Might help you with your issue.
Android: findViewById of an ImageView (custom adapter)
Android : getting NullPointerException for ImageView imag = (ImageView) findViewById(R.id.image)
try getLayoutResource to get the View of the preference and then get your ImageView
Related
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);
}
...
I'm trying to create a re-usable control that I can just add whereever I like with already existing controls so I need no attributes, need nothing to add.
I just want the control to show up my Visual Editor when I drag the "Custom View" to the Layout.
I have a simple view_textseek.xml as example Layout that I don't want to recreate everytime I want "Text and a Seekbar" in case I use it on 3 different places (for example: a colorpicker later). Or just a control with both a "TextView" and a "SeekBar"
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
android:id="#+id/view_textseek_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:paddingLeft="15dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:text="#string/view_textseek_text"
android:textAppearance="?android:attr/textAppearanceMedium" />
<RelativeLayout
android:id="#+id/view_textseek_container_seekbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="#+id/view_textseek_text"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:paddingRight="10dp"
android:paddingBottom="5dp" >
<SeekBar
android:id="#+id/view_textseek_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:max="255"
android:progress="0" />
</RelativeLayout>
</RelativeLayout>
and this is basically my class:
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.RelativeLayout;
public class TextSeekView extends RelativeLayout
{
public TextSeekView(Context context) { super(context); init(context); }
public TextSeekView(Context context, AttributeSet attrs) { super(context, attrs); init(context); }
public TextSeekView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); }
protected void init(Context context)
{
if (!isInEditMode())
{
LayoutInflater layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutInflater.inflate(R.layout.view_textseek, this, true);
}
}
}
As you can see, I just want to "collect" multiple already existing controls and have one "View" or "Control" to handle these. It does not show up in the editor.
Or is this due to the some old type of idiotic bug that you have to restart your environment for "custom" Views to be loaded correctly?
And I need no special attributes on my other views, nothing at all and I just want to be able to show this layout again and again when adding it, or any other layout.
Like C#, add 3 textboxes to a control just cause you use 3 textboxes each time. Then, drag that control out on the Form whenever you want 3 textboxes - nothing more to it!
Change this code:
protected void init(Context context) {
LayoutInflater layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutInflater.inflate(R.layout.view_textseek, this, true);
if (!isInEditMode()) {
// isInEditMode returns true when you show a view on graphical editor. Returns false while showing on running app.
}
}
I've implemented a custom view with hosts two subviews which are identified by an id in the xml. When using two of this custom view in the same layout I run into the problem that it is random which custom view is chosen.
How can I write a custom view with different view ids that can be multiply used in the same layout?
Here is the xml of the custom view:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<EditText
android:id="#+id/clearable_edit"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords"
android:paddingRight="35dip" />
<Button
android:id="#+id/clearable_button_clear"
android:layout_width="30dip"
android:layout_height="30dip"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="5dip"
android:background="#drawable/clear_button" />
</RelativeLayout>
The id (android:id="#+id/clearable_edit") of the EditText is the problem here.
Usage of custom view:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<com.custom.package.ClearableEditText
android:id="#+id/arr_location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
</com.custom.package.ClearableEditText>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<com.custom.package.ClearableEditText
android:id="#+id/dep_location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
</com.custom.package.ClearableEditText>
</LinearLayout>
In this example the views of type "ClearableEditText" share the same id of their EditText subview.
Here is the code for ClearableEditText:
public class ClearableEditText extends RelativeLayout {
private LayoutInflater inflater = null;
private EditText edit_text;
private Button btn_clear;
public ClearableEditText(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
initViews();
}
public ClearableEditText(Context context, AttributeSet attrs){
super(context, attrs);
initViews();
}
public ClearableEditText(Context context){
super(context);
initViews();
}
private void initViews(){
inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.clearable_edittext, this, true);
edit_text = (EditText) view.findViewById(R.id.clearable_edit);
btn_clear = (Button) findViewById(R.id.clearable_button_clear);
btn_clear.setVisibility(RelativeLayout.INVISIBLE);
}
}
First fetch parent View like this:
View v1 = findViewById(R.id.arr_location);
and then
EditText ed1 = (EditText)v1.findViewById(R.id.clearable_edit);
Similarly
View v2 = findViewById(R.id.dep_location);
EditText ed2 = (EditText)v2.findViewById(R.id.clearable_edit);
This way you can add as many ClearableEditText as you want having same id for EditText and Button. Just make sure that every ClearableEditText has different id e.g. in this case R.id.arr_location and R.id.dep_location.
I've found a solution.
I've added a method to ClearableEditText where you can set the id of the underlying EditText from outside the object and set it with a new id.
Here is a code sample:
//inside ClearableEditText
public void setEditId(int id){
edit_text.setId(id);
}
//somewhere else
departureLocation = (ClearableEditText)view.findViewById(R.id.dep_location);
departureLocation.setEditId(R.id.clearable1);
arrivalLocation = (ClearableEditText)view.findViewById(R.id.arr_location);
arrivalLocation.setEditId(R.id.clearable2);
The Ids are created with a "ids.xml" in the values folder, which causes eclipse/ADT to create a placeholder id for the entered items
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- This file is used to create unique ids for custom views, which will be used more
than once in the same layout file. Using unique ids prevents the custom view from getting
the wrong state. -->
<item name="clearable1" type="id"></item>
<item name="clearable2" type="id"></item>
</resources>
Sorry if this redundant with the ton of questions/answers on inflate, but I could not get a solution to my problem.
I have a compound view (LinearLayout) that has a fixed part defined in XML and additional functionalities in code. I want to dynamically add views to it.
Here is the XML part (compound.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/compoundView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView android:id="#+id/myTextView"
android:layout_width="110dp"
android:layout_height="wrap_content"
android:text="000" />
</LinearLayout>
I have defined in code a LinearLayout to refer to the XML:
public class CompoundControlClass extends LinearLayout {
public CompoundControlClass (Context context) {
super(context);
LayoutInflater li;
li = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
li.inflate(R.layout.compound_xml,*ROOT*, *ATTACH*);
}
public void addAView(){
Button dynBut = new Button();
// buttoin def+layout info stripped for brevity
addView(dynBut);
}
}
I tried to programmatically add a view with addAView.
If ROOT is null and ATTACH is false, I have the following hierarchy (per HierarchyViewer):
CompoundControlClass>dynBut
The original TextView in the XML is gone.
If ROOT is this and ATTACH is true, I have the following hierarchy:
CompoundControlClass>compoundView>myTextView
CompoundControlClass>dynBut
I would like to have
CompoundControlClass>myTextView
CompoundControlClass>dynBut
where basically the code and XML are only one unique View.
What have I grossly missed?
ANSWER BASED on feedback from D Yao ----------------------
The trick is to INCLUDE the compound component in the main layout instead of referencing it directly.
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="#layout/comound"
android:id="#+id/compoundView"
android:layout_alignParentBottom="true"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
mainActivity.java
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CompoundControlClass c = (CompoundControlClass) this.findViewById(R.id.compoundView);
c.addAView(this);
}
}
CompoundControlClass.java
public class CompoundControlClass extends LinearLayout {
public CompoundControlClass(Context context) {
super(context);
}
public CompoundControlClass(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CompoundControlClass(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void addAView(Context context){
ImageView iv = new ImageView(context);
iv.setImageResource(R.drawable.airhorn);
addView(iv);
}
}
compound.xml
<com.sounddisplaymodule.CompoundControlClass xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/compoundView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:layout_width="110dp"
android:layout_height="wrap_content"
android:gravity="right"
android:textSize="40sp"
android:textStyle="bold"
android:text="0:00" />
</com.sounddisplaymodule.CompoundControlClass>
Why not just call addView on the linearlayout? I don't see the need for CompoundControlClass based on the needs you have listed.
LinearLayout v = (LinearLayout)findViewById(R.id.compoundView);
v.addView(dynBut);
In this case, v will contain myTextView, then dynBut.
if you wish to have other functions added and thus really feel a need for creating the compound control class, just leave the constructor as super(etc) and remove the rest
Then your xml would look like this:
<com.yourpackage.CompoundControlClass xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/compoundView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView android:id="#+id/myTextView"
android:layout_width="110dp"
android:layout_height="wrap_content"
android:text="000" />
</com.yourpackage.CompoundControlClass>
you will also have to ensure your CompoundControlClass.java contains the appropriate Constructor which takes both a Context and an attribute set.
Then, in your java, after you've called setContentView, you can do the following:
CompoundControlClass c = (CompoundControlClass)findViewById(R.id.compoundView);
Button b = new Button(context);
//setup b here or inflate your button with inflater
c.addView(b);
this would give you your desired heirarchy.
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()
}
}