It is possible to pass a Class<> object in Android Layout XML? - android

I am building a custom View that requires as one of its Attributes a Class<> object to an entity. While I made it work programmatically by adding a Setter for it, I was wondering if there is any good way to allow adding it to the XML for the layout as well?
There does not appear to be a format option for a styleable with type "class". I could use a String, but then I'd have to gamble that the value is actually a valid Class and I'd lose type hinting, so it'd not be ideal.
Is there any good way to make this work, or should I just stick with setting it programmatically?

Method 1 (With warnings):
Generic CustomView:
public class CustomView<T> extends View {
private List<T> typedList = new ArrayList<T>();
public CustomView(Context context) {
this(context, null);
}
public CustomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void addTypedValue(T object){
typedList.add(object);
}
public T getTypedValue(int position){
return typedList.get(position);
}
}
Activity:
//unsafe cast!
CustomView<String> customViewGeneric = (CustomView<String>) findViewById(R.id.customView);
customViewGeneric.addTypedValue("Test");
String test = customViewGeneric.getTypedValue(0);
XML:
<org.neotech.test.CustomView
android:id="#+id/customView"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
Method 2 (No warnings, safe!):
This method uses a generic CustomView. And for each type that will be used in xml you will need to create a specific class.
I have added an example implementation:
Generic CustomView: (Do not inflate this one in xml):
public class CustomView<T> extends View {
private List<T> typedList = new ArrayList<T>();
public CustomView(Context context) {
this(context, null);
}
public CustomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void addTypedValue(T object){
typedList.add(object);
}
public T getTypedValue(int position){
return typedList.get(position);
}
}
XML inflatable view for the String type:
public class CustomViewString extends CustomView<String> {
//ADD Constructors!
}
XML inflatable view for the Integer type:
public class CustomViewInteger extends CustomView<Integer> {
//ADD Constructors!
}
Activity:
CustomViewString customViewString = (CustomViewString) findViewById(R.id.customViewString);
CustomView<String> customViewGeneric = customViewString;
XML:
<org.neotech.test.CustomViewString
android:id="#+id/customViewString"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<org.neotech.test.CustomViewInteger
android:id="#+id/customViewInteger"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

Related

Connect java class to included xml android

I have a xml layout with an element i use multiple times... so i decided to use <include> in the xml to avoid excess code.
My problem is that i want to make a class that is connected to this included xml component and reference to it, rather than writing the same code multiple times.
I tried to read up on a custom view component and created a class ParcelPopbarView:
public ParcelTopBarView extends View {
/...
public ParcelTopBarView(Context context, ParcelListItem parcelListItem) {
super(context);
this.parcelListItem = parcelListItem;
this.context = context;
titleTextView = findViewById(R.id.title_textview);
subtitleTextView = findViewById(R.id.subtitle_textview);
deliveryInfoTextView = findViewById(R.id.delivery_info_textview);
thumbnailLogo = findViewById(R.id.thumbnail_logo);
}
public void setTopbar(){
titleTextView.setText("hello!");
}
But i felt like custom views mostly is about drawing on a canvas, i am not doing that... so i dont know if using view is right either.
Either way, the titleTextView is null cause it cannot find the refrence to the xml file, and i have no idea how to reference to it hahah.
Does anyone have a smart solution to how i can do this the right way?
You need to inflate the layout for all the constructors of your custom view to be able to access them later.
public class ParcelTopBarView extends View {
public ParcelTopBarView(Context context) {
super(context);
init();
}
public ParcelTopBarView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ParcelTopBarView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public ParcelTopBarView(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
View view = inflate(getContext(), R.layout.parcel_top_bar, this);
titleTextView = findViewById(R.id.title_textview);
subtitleTextView = findViewById(R.id.subtitle_textview);
deliveryInfoTextView = findViewById(R.id.delivery_info_textview);
thumbnailLogo = findViewById(R.id.thumbnail_logo);
}

Custom view (not ViewGroup) created based on xml layout (with predefined attributes)

I want to implement a custom ImageView with some predefined attributes based on the xml file. To do that I prepared a layout wrapped within merge tag:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:id="#+id/my_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="center"
android:src="#drawable/icon" />
</merge>
And extended ImageView class:
public class CustomImageView extends LinearLayout{
public ImageFormField(Context context) {
super(context);
init();
}
public ImageFormField(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ImageFormField(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public ImageFormField(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
View.inflate(getContext(), R.layout.custom_image_view, this);
}
}
It works so far, but I actually don't need that LinearLayout as I could extend directly from the ImageView. By extending ImageView I'd like to have the possibility to override src parameter from the default layout.
So I removed merge tag to have only ImageView in the layout and tried this:
public class CustomImageView extends AppCompatImageView{
public ImageFormField(Context context) {
super(context);
init();
}
public ImageFormField(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ImageFormField(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
View.inflate(getContext(), R.layout.custom_image_view, null); //can't pass root here
}
}
... but the image is simply not displayed. I want to be able to use my view this way:
<com.my.package.CustomImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
with possibility to override src attribute. Is there a way to do that by inflating layout or do I have to go deep with attributes (including custom ones)?
UPDATE
By "overriding src attribute I mean that by default image will have source from its xml file, but user can use it that way to pass another value within this custom view:
<com.my.package.CustomImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/another_icon" />
To provide a "default" image but also allow the user to override that default by specifying the android:src attribute, you can do this:
package com.example.stackoverflow;
import android.content.Context;
import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;
public class CustomImageView extends AppCompatImageView {
public CustomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
if (getDrawable() == null) {
setImageResource(R.drawable.default_image);
}
}
}
Then you can use it like this:
<!-- will display `default_image` -->
<com.example.stackoverflow.CustomImageView
android:layout_width="200dp"
android:layout_height="200dp"/>
Or:
<!-- will display `other_image` -->
<com.example.stackoverflow.CustomImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:src="#drawable/other_image"/>
There's no need to inflate anything inside the custom image view, and no need to create custom attributes (though you could certainly create custom attributes and use them if you wanted to).
You could update the Java code to apply other "default" styles too.

What is correct way to make custom view based on EditText?

What is correct way to extend EditText?
The problem is following:
I have empty application from template with two EditText:
<?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="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="one"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="two"/>
</LinearLayout>
It works fine:
Then I create my custom view from EditText:
public class CuteEditText extends EditText {
public CuteEditText(Context context) {
this(context, null);
}
public CuteEditText(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CuteEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// some special initialization will be here
}
}
And when I change EditText to my CuteEditText, interface works incorrectly:
The problem is not only with view ot UI. If I type something in first EditText and than touch the second one, nohing happens: input will continue in first.
The same behaviour if I inherite CuteEditText from AppCompatEditText.
What is wrong?
Sources for experiment are available at https://github.com/tseglevskiy/EditTextExperiment
Your construtors are broken. This is how it should look:
public class CuteEditText extends EditText {
public CuteEditText(Context context) {
super(context);
}
public CuteEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CuteEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
You don't need the third constructor overload. The first one is for creating the view programmatically and the second one is for creating the view from xml. Those two should be enough for most cases.
public class CuteEditText extends EditText {
public CuteEditText(Context context) {
this(context, null);
}
public CuteEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
}

Extending view group gives childcount 0?

I am extending a frame layout
and when in the constructor (after it loaded the view from xml) i call getChildCount()
I get 0. How to fix this ?
public class DisabledFrameLayout extends FrameLayout {
public DisabledFrameLayout(Context context) {
super(context);
init(context, null,0);
}
public DisabledFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context,attrs,defStyle);
}
public DisabledFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context,attrs,0);
}
private void init(Context context, AttributeSet attrs, int defStyle) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DisabledFrameLayout, 0, 0);
if(a.hasValue(R.styleable.DisabledFrameLayout_disable_descendants)) {
boolean disable = a.getBoolean(R.styleable.DisabledFrameLayout_disable_descendants, false);
if(disable) {
disableDescendants(this);
}
}
a.recycle();
}
private void disableDescendants(ViewGroup v) {
for (int i=0; i<v.getChildCount();i++) {
if(v.getChildAt(i) instanceof ViewGroup) {
disableDescendants((ViewGroup)v.getChildAt(i));
}
v.setEnabled(false);
v.setFocusable(false);
v.setFocusableInTouchMode(false);
}
}
}
xml
<com.lenabru.DisabledFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:disable_descendants="true"
>
<include
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
layout="#layout/fragment_p" />
</com.lenabru.DisabledFrameLayout>
In state of constructing your FrameLayout, children are still not fully attached to the view. So, calling getChildCount() will return you 0.
If you want to iterate over child views and update them, do it inside onLayout() or onMeasure().
Refs:
http://blog.denevell.org/android-custom-views-onlayout-onmeasure.html
ViewGroup - check this example code.

Removing extra view created when using a custom view

Using the following code:
public class CustomView extends RelativeLayout {
public CustomView(Context context) {
this(context, null, 0);
}
public CustomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
inflate(getContext(), R.layout.custom_view, this);
}
}
The layout is simple:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/custom_view_id"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f00"/>
The hierarchyviewer shows the following:
The CustomView hierarchy is useless and I would like to remove it.
Is there a way to create a custom view extending a ViewGroup without adding that additional View?
If your CustomView is already a RelativeLayout in you XML layout you can just delete the RelativeLayout with "#+id/custom_view_id" and use as father the tag
That will merge the children with the CustomView without using an extra RelativeLayout.
The CustomView will setBackgroundColor
public class CustomView extends RelativeLayout {
public CustomView(Context context) {
this(context, null, 0);
}
public CustomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
setBackgroundColor(Color.parseColor("#f00"));
inflate(getContext(), R.layout.custom_view, this);
}}
and the layout xml file will be:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<!-- children here -->
</merge>

Categories

Resources