Custom view with already existing controls - android

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.
}
}

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.

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);
}
...

Dynamically adding views to mixed xml/code compound layout

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.

Problem with creating Compound Control in Android

I am learning to create a compound control in android.
For starters i tried an edit text with an attached button to clear it.
The problem is even though i can see the compound control in the graphical view of the
main.xml, there is an error message : "Custom view ClearableEditText is not using the 2- or 3-argument View constructors; XML attributes will not work"
This is not visible in project explorer under errors only in the xml graphical view
i am able to compile and run but get a force close.
XML : COMPOUND CONTROL clearable_edit_text.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<EditText android:id="#+id/editText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<Button android:id="#+id/clearButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CLEAR"
/>
</LinearLayout>
CLASS
public class ClearableEditText extends LinearLayout
{
EditText et;
Button btn;
public ClearableEditText(Context context)
{
super(context);
LayoutInflater li=(LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
li.inflate(R.layout.clearable_edit_text,this,true);
et=(EditText)findViewById(R.id.editText);
btn=(Button)findViewById(R.id.clearButton);
hookupButton();
}
private void hookupButton()
{
btn.setOnClickListener(new Button.OnClickListener()
{
public void onClick(View v)
{
et.setText("");
}
});
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<com.commsware.android.merge.ClearableEditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<com.commsware.android.merge.ClearableEditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
Your class extends LinearLayout but you never add any views to it. You need to call addView(...) and pass your inflated view as the parameter.
Also, to define your view in XML you need to override the 2 and 3 argument constructors for a LinearLayout. Add this to your code:
public ClearableEditText(Context context, AttributeSet attrs) {
super( context, attrs );
}
public ClearableEditText(Context context, AttributeSet attrs, int defStyle) {
super( context, attrs, defStyle );
}
To get all 3 constructors to use the same initialization code, move your code from the single argument constructor to the 3 argument constructor, then in the other 2 constructors call this(context, null, 0) and this(context, attrs, 0) respectively.

Using icons in Android preferences

I would like some of my preferences to have icons, like the Settings app.
I guess one way of doing this would be to copy all the relevant code and resources from the Settings app, but it seems like overkill for a couple of icons.
Also I don't like the idea of having to duplicate the code and resources in each project that requires settings icons.
Has anyone solved this already with a simpler or resource-free approach?
The Settings application uses a private custom PreferenceScreen subclass to have the icon -- IconPreferenceScreen. It is 51 lines of code, including the comments, though it also requires some custom attributes. The simplest option is to clone all of that into your project, even though you do not like that.
source code
Since API level 11 you can add icon to preferences:
http://developer.android.com/reference/android/preference/Preference.html#setIcon%28int%29
http://developer.android.com/reference/android/preference/Preference.html#attr_android:icon
For older Android versions, you have to use custom layouts and bind images in code, as #roger-l suggests.
Updated.... answered and working
Use a custom layout for the icon preference
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+android:id/widget_frame"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:paddingRight="?android:attr/scrollbarSize">
<ImageView
android:id="#+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="6dip"
android:layout_marginRight="6dip"
android:layout_gravity="center" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="2dip"
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:maxLines="2" />
</RelativeLayout>
</LinearLayout>
and the imported class for IconPreferenceScreen
public class IconPreferenceScreen extends Preference {
private final Drawable mIcon;
private static String mType;
public IconPreferenceScreen(Context context, AttributeSet attrs, int iconRes) {
this(context, attrs, 0, iconRes);
}
public IconPreferenceScreen(Context context, AttributeSet attrs,
int defStyle, int iconRes) {
super(context, attrs, defStyle);
setLayoutResource(R.layout.preference_icon);
mIcon = context.getResources().getDrawable(iconRes);
}
public IconPreferenceScreen(Context context, int iconRes) {
this(context, null, iconRes);
}
#Override
public void onBindView(View view) {
super.onBindView(view);
ImageView imageView = (ImageView) view.findViewById(R.id.icon);
if (imageView != null && mIcon != null) {
imageView.setImageDrawable(mIcon);
}
}
}
then you can just use a new IconPreferenceScreen in place of a Preference, and add an icon
Android 3.x Honeycomb shows icons defined in standard Preferences.
So probably the use of icon depends on Android OS version or screen size.

Categories

Resources