android custom component

android logo

I recently created an android custom component. To get started, I simply wanted to use the android framework’s CheckedTextView as the starting point. This is because I had an existing project having walked through a tutorial video on safari.oreilly.com. In the tutorial, CheckedTextView was used to create a custom list item for a ListView. I wanted to change the list item to replace the checkbox with something else.

How hard could it be?

I cloned the framework source code

git clone git://android.git.kernel.org/platform/frameworks/base.git

extracted the class’ source,

~/base/core/java/android/widget/CheckedTextView.java

and created a new class with the CheckedTextView source and changed the package name.

package com.example.proj.widget;
    ⋮
public CheckedTextView(
  Context context, AttributeSet attrs, int defStyle) {
    ⋮
}

I had four problems to solve just to get the class to compile.

  1. import
  2. initialization
  3. constructor
  4. superclass

Problem 1: import

// ERROR: The import com.android.internal.R cannot be resolved
import com.android.internal.R;

Solution 1:

You cannot import the internal android class. But you will need both android’s public R class and your own project’s R class. Remove the internal import.

// Okay!
//import com.android.internal.R;

Problem 2: initialization

private static final int[] CHECKED_STATE_SET = {
  // ERROR: R cannot be resolved
  R.attr.state_checked
};

Solution 2:

Provide the android namespace for R.

private static final int[] CHECKED_STATE_SET = {
  // Okay!
  android.R.attr.state_checked
};

Problem 3: constructor

public CheckedTextView(
  Context context, AttributeSet attrs, int defStyle) {

    super(context, attrs, defStyle);

    TypedArray a = context.obtainStyledAttributes(
      attrs,
      // ERROR: R.styleable cannot be resolved
      R.styleable.CheckedTextView,
      defStyle, 0);

    Drawable d = a.getDrawable(
      // ERROR: R.styleable cannot be resolved
      R.styleable.CheckedTextView_checkMark);

    if (d != null) {
        setCheckMarkDrawable(d);
    }

    boolean checked = a.getBoolean(
      // ERROR: R.styleable cannot be resolved
      R.styleable.CheckedTextView_checked,
      false);

    setChecked(checked);
    a.recycle();
}

Solution 3:

In the android source files, check and checkMark attributes are publicly defined by android so they’re okay.

~/base/core/res/res/values/public.xml
<resources>
  <!--
    ⋮
    -->
  <publictype="attr"name="checked"id="0x01010106"/>
  <publictype="attr"name="button"id="0x01010107"/>
  <publictype="attr"name="checkMark"id="0x01010108"/>
  <!--
    ⋮
    -->
</resources>

CheckedTextView is defined by android but it is not available for import!

~/base/core/res/res/values/attrs.xml
<resources>
  <!--
    ⋮
    -->
  <declare-styleable name="CheckedTextView">
    <!-- Indicates the initial checked state of this text. -->
    <attr name="checked" />
    <!-- Drawable used for the check mark graphic. -->
    <attr name="checkMark" format="reference" />
  </declare-styleable>
  <!--
    ⋮
    -->
</resources>

The solution is to fetch pull the styleable definition out of the android source code and place in your own XML file.

In your eclipse project, create (or update) the XML file res/values/attrs.xml

<resources>
  <declare-styleable name="MyCheckedTextView">
    <attr name="android:checked" />
    <attr name="android:checkMark" />
  </declare-styleable>
</resources>

I belive that the local filename isn’t relevant but all the examples I saw used attrs.xml. The attribute names need the android: prefix as they have already been defined in the framework.

Notice that in my custom attrs.xml file, I didn’t include the format attribute on android:checkMark. This is because checkMark has already been defined in the framework; we are just referencing it here.

Since this is a custom definition, I changed the name of my class to MyCheckedTextView (and, of course, changed the constructor names accordingly.)

// old class declaration
public class CheckedTextView extends TextView implements Checkable {
// new class declaration
public class MyCheckedTextView extends TextView implements Checkable {

Finally, change the constructor code to use your project’s R class rather than android’s R class.

public MyCheckedTextView(
  Context context, AttributeSet attrs, int defStyle) {

    super(context, attrs, defStyle);

    TypedArray a = context.obtainStyledAttributes(
      attrs,
      // Okay!
      com.example.proj.R.styleable.MyCheckedTextView,
      defStyle, 0);

    Drawable d = a.getDrawable(
      // Okay!
      com.example.proj.R.styleable.MyCheckedTextView_checkMark);

    if (d != null) {
        setCheckMarkDrawable(d);
    }

    boolean checked = a.getBoolean(
      // Okay!
      com.example.proj.R.styleable.MyCheckedTextView_checked,
      false);

    setChecked(checked);
    a.recycle();
}

Problem 4: superclass

mPaddingRight is a protected field in the View class. For some reason, it isn’t visible.


    mCheckMarkWidth = d.getIntrinsicWidth();
    // ERROR: mPaddingRight cannot be resolved
    mPaddingRight = mCheckMarkWidth + mBasePaddingRight;
    d.setState(getDrawableState());
  } else {
    // ERROR: mPaddingRight cannot be resolved
    mPaddingRight = mBasePaddingRight;
  }
  mCheckMarkDrawable = d;
  requestLayout();
}

@Override
public void setPadding(
  int left, int top, int right, int bottom) {
  super.setPadding(left, top, right, bottom);
  // ERROR: mPaddingRight cannot be resolved
  mBasePaddingRight = mPaddingRight;
}

Solution 4:

The superclass has a getter available but no setter. Use super’s getter and create a local setter that wraps super’s more generic setPadding method.

public void setCheckMarkDrawable(Drawable d) {
  if (mCheckMarkDrawable != null) {
    mCheckMarkDrawable.setCallback(null);
    unscheduleDrawable(mCheckMarkDrawable);
  }
  if (d != null) {
    d.setCallback(this);
    d.setVisible(getVisibility() == VISIBLE, false);
    d.setState(CHECKED_STATE_SET);
    setMinHeight(d.getIntrinsicHeight());
        
    mCheckMarkWidth = d.getIntrinsicWidth();
    // Okay!
    setPaddingRight(mCheckMarkWidth + mBasePaddingRight);
      d.setState(getDrawableState());
  } else {
    // Okay!
    setPaddingRight(mBasePaddingRight);
  }
  mCheckMarkDrawable = d;
  requestLayout();
}
    
// setPadding(
//     leftPadding   >= 0 ? leftPadding   : mPaddingLeft,
//     topPadding    >= 0 ? topPadding    : mPaddingTop,
//     rightPadding  >= 0 ? rightPadding  : mPaddingRight,
//     bottomPadding >= 0 ? bottomPadding : mPaddingBottom
// );
private void setPaddingRight(int padding) {
  //  Update: use super's setter
  //  or right padding will be screwed up 
  //  setPadding(0, 0, padding, 0);
  super.setPadding(0, 0, padding, 0);
}

@Override
public void setPadding(
  int left, int top, int right, int bottom) {
  super.setPadding(left, top, right, bottom);

  // Okay!
  mBasePaddingRight = getPaddingRight();
}

It now compiles. The checkbox doesn’t render in exactly the same place as does the actual android version of CheckedTextView. I suspect that the source code from the git repository doesn’t correspond with the SDK. Everything functions but the right padding is off. Update: fixed, see problem 4.

I couldn’t have done this without help from those who’ve gone before:

  • How do I use obtainStyledAttributes(int []) with internal Themes of Android stackoverflow
  • Android Hello, Gallery tutorial — “R.styleable cannot be resolved” stackoverflow
  • Declaring a custom android UI element using XML stackoverflow
  • How to retrieve XML attribute for custom control stackoverflow

3 Comments:

  1. Please Could give me the project of custom component ….if long tried to implement this but not getting a good sample to implement it.

    Amrita

    2011.02.09
    04:41

  2. @Amrita — I’m not sure what you are asking. I cannot send the entire project as it was work-for-hire. What are you trying to implement?

    kelly

    2011.02.09
    06:46

  3. Hi,
    I want to do something similar: implementing a custom clock widget.
    I tried the above approach with the stock clock and have a problem: some of the attributes are not public (as is the case for you in Problem 3). Hence, they give an error that it cannot be resolved.
    Also, there are a bunch of errors as it cannot resolve R.drawable.something (eg. R.drawable.clock_hand_minute)? This file doesn’t exist in frameworks/base/core/res/res/drawable/
    Thanks for your help.

    Deepanshu

    2012.07.23
    05:26

Your email will never published nor shared. Required fields are marked *...

*

*

Type your comment out: