home.


Tagged: android-custom-view


Android: Custom View's onLayout and onMeasure methods

If you want to layout a custom view’s children yourself, as opposed to making the custom view extend LinearLayout or similar, you need to implement the onLayout and onMeasure methods of the ViewGroup class.

onLayout()

Let’s first look at onLayout(). This tells your custom view’s children where they should lay themselves out:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  childCount = getChildCount();
  for(int i=0; i<childCound;i++) {
    View v = getChildAt(i);
    ...
  }
}

This method is called by the ViewGroup, passing in the parent’s dimensions.

The ‘left’ and ‘top’ is the left and right from the custom view’s parent, this is normally zero unless you have set margins on your custom view.

Then we get the number of children and are about to process each in the loop.

The method we must call on each of the views’ in the loop is

v.layout(left, top, right, bottom). 

If ‘left’ or ‘top’ are 0 this means right at the left and top edge of the parent.

If you have defined some padding on your custom view, you must include these in your layout call, otherwise the padding will be in front of the view.

onMeasure()

Before you can call methods such like getMeasuredWidth() on your ViewGroup’s children, however, you must tell them how to measure themselves in onMeasure:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  ...
}

The two parameters passed in are int values which represent the mode and side of the width and height.

Let’s say you passed in match_parent to the layout_width parameter in your View’s XML, and the width of your view’s parent is 400, then the following would be true:

int mode = MeasureSpec.getMode(widthMeasureSpec) // mode == View.MesaureSpec.EXACTLY
int size = MeasureSpec.getSize(widthMeasureSpec) // size == 400

In this method you must call either its super method or setMeasuredDimension() with the width specifications and height specification either passed into the method or created with MeasureSpec.makeMeasureSpec(size, mode).

You must also call the measure(widthSpec, heightSpec) on the child views too.

  for(int i=0; i<childCound;i++) {
    View v = getChildAt(i);
    v.measure(widthSpec, heightSpec);
  }

If you wanted to give each view the same measurements as its parents (BUT WHY???) you could give it its parent’s measure specs, or create your own via the makeMeasureSpec above, perhaps by using a division of the parent’s getMeasuredWidth(), taking into account any padding using the getPadding*() methods.

android android-custom-view

Android: Passing objects to custom views with databinding

Update: There’s now a much, much simplier version of this.

This is a bit voodoo, but that said… Let’s say you include something like this as a static method anywhere in your codebase.

@BindingAdapter("app:thing")
public static void setThing(View v, Object s) {
  Log.d("A log, innit", "Called setThing");
}

Note that the method name setThing is derived from app:thing. And the first parameter is a View and the second is an Object.

And then, in a databinding layout file, have something like

<layout xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:app="http://schemas.android.com/apk/res-auto">
  <data>
    <variable
        name="thing"
        type="String"/>
  </data>
  <com.example.blar.myapplication.CustomView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      app:thing="@{thing}"
      />
  ...

Then the above static method will be called when we try to set the app:thing attribute.

If in your static method you have something like this:

CustomViewDatabindingSettable st = (CustomViewDatabindingSettable) v;
st.passedDataBindingObject(s);

Then, providing your custom view implements CustomViewDatabindingSettable, you can call passedDataBindingObject passing through the databound variable.

For example, your custom view could be:

public class ListView extends FrameLayout implements CustomViewDatabindingSettable {
  ...
  @Override
  public void passedDataBindingObject(Object o) {
    Log.d("HIYA", "We're in passedDataBindingObject: " + o);
    // Now do something with the data bound object.
  }
  ...
}

Databinding and ‘instant run’ seems to mess up the autogeneration sometimes, in Android Studio 2.0 beta anyhow.

I had to uninstall the project from the device occassionally to remove dead code.

android android-databinding android-custom-views

Android: Simpler passing objects to custom views with databinding

If you have a custom view, you can pass an object to it via databinding. Let’s say you have this basic custom view.

public class CustomV extends Button {

  public static class APojo {
    private String oj;

    public String getOj() {
     return oj;
    }

    public void setOj(String oj) {
     this.oj = oj;
     }
  }

  private APojo thing;

  public CustomV(Context context) {
    super(context);
    init(null, 0);
  }

  public CustomV(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(attrs, 0);
  }

  public CustomV(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(attrs, defStyle);
  }

  private void init(AttributeSet attrs, int defStyle) {
    setText("Custom innit");
  }

  public APojo getThing() {
    return thing;
  }

  public void setThing(APojo thing) {
    this.thing = thing;
  }
}

It’s a basic custom view class, with a basic pojo class at at the top, a class variable and a getter and setter for that.

Now initialise this custom view:

<com.example.CustomV
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  app:thing="@{somevar.someinstance}"
/>

The somevar is just a databinding variable instance you have. someinstance is an instance of the APojo class. And the app: namespace is the standard xml:app="http://schemas.android.com/apk/res-auto".

Now when you initalise that view, the setThing method in your custom view will be called.

android android-databinding android-custom-views

Android: Custom views and attributes

First create a class that extends a View, like FrameLayout here. It inflates a normal layout. You can skip that if you extends a TextView or something.

 public class CustomView extends FrameLayout {

  public CustomView(Context context) {
    super(context);
  }

  public CustomView(Context context, AttributeSet attrs) {
    super(context, attrs);
    LayoutInflater layoutInflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View layout = layoutInflator.inflate(R.layout.generic_error_overlay, this);
  }

  @Override
  public void onRestoreInstanceState(Parcelable state) {
    if (state instanceof Bundle) {
      Bundle bundle = (Bundle) state;
      // Restore things from bundle here
      super.onRestoreInstanceState(bundle.getParcelable("instanceState"));
      return;
    }
    super.onRestoreInstanceState(state);
  }

  @Override
  protected Parcelable onSaveInstanceState() {
    Bundle bundle = new Bundle();
    bundle.putParcelable("instanceState", super.onSaveInstanceState());
    // Add things to bundle here
    return bundle;
  }

 }

The onSaveInstanceState / onRestoreInstanceState methods allow you to save the View’s state.

To use this in a layout you can do something like:

<your.package.where.the.view.lives.CustomView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content" />

If you want to pass custom attributes, first define the XML namespace in the root element in your layout file:

xmlns:yournamespace=“http://schemas.android.com/apk/res-auto
Then you can use a custom attribute in your XML custom view:

<your.package.where.the.view.lives.CustomView
 android:layout_width="wrap_content"
 yournamespace:your_attribute="Hello"
 android:layout_height="wrap_content" />

You then need to define this attribute in attrs.xml. We’ll make this one a string.

<resources>
...
 <declare-styleable name="YourAttribute">
     <attr name="your_attribute" format="string"></attr>
 </declare-styleable>
...
</resources>

Then in the constructor for your custom view, you can grab this:

...
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.YourAttribute, 0, 0);
try {
 String string = a.getString(R.styleable.YourAttribute_your_attribute);
} finally {
 a.recycle();
}   
...

Note we’re recycling the TypedArray as it’s a shared object. We also refer the the generated styleable attributes in gen that are generated when you added values on attrs.xml.

android android-custom-views

Page 1 of 1