android custom component
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.
- import
- initialization
- constructor
- 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