I have a ListView, which is in singleChoice mode. All I want is to display a RadioButton to the side, that when clicked highlights to say it is selected, and when a differen
EDIT
Worth mentioning is that I had a custom layout on my list items: There is an icon, a title, a description and then the checkbox or radio button (depending on if it's a single or multiple choice list). My example solution is, hence described by no less than three different parts:
ListView
configurationAdapter::getView()
implementation.
So let's get to the magic then, shall we?
listitem.xml
<com.dbm.CheckableLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:id="@+id/myIcon" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@android:style/TextAppearance.Medium"
android:textStyle="bold"
android:ellipsize="end"
android:id="@+id/myTitle" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@android:style/TextAppearance.Small"
android:textStyle="italic"
android:ellipsize="end"
android:id="@+id/myDescr" />
</LinearLayout>
<CheckedTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@android:id/text1" />
</com.dbm.CheckableLinearLayout>
CheckableLinearLayout.java
public class CheckableLinearLayout extends LinearLayout implements Checkable {
private CheckedTextView mCheckedTextView;
private final Drawable mCheckDrawable;
private final Drawable mRadioDrawable;
private boolean mIsChecked;
/**
* Constructor.
*
* @param context The context to operate in.
* @param attrs The attributes defined in XML for this element.
*/
public CheckableLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = null;
// Cache the check box drawable.
typedArray = context.getTheme().obtainStyledAttributes(new int[] {android.R.attr.listChoiceIndicatorMultiple});
if ((typedArray != null) && (typedArray.length() > 0)) {
mCheckDrawable = typedArray.getDrawable(0);
}
else {
// Fallback if the target theme doesn't define a check box drawable.
// Perhaps an application specific drawable should be used instead of null.
mCheckDrawable = null;
}
// Careful with resources like this, we don't need any memory leaks.
typedArray.recycle();
// Cache the radio button drawable.
typedArray = context.getTheme().obtainStyledAttributes(new int[] {android.R.attr.listChoiceIndicatorSingle});
if ((typedArray != null) && (typedArray.length() > 0)) {
mRadioDrawable = typedArray.getDrawable(0);
}
else {
// Fallback if the target theme doesn't define a radio button drawable.
// Perhaps an application specific drawable should be used instead of null
mRadioDrawable = null;
}
// Careful with resources like this, we don't need any memory leaks.
typedArray.recycle();
mIsChecked = false;
}
/*
* (non-Javadoc)
* @see android.widget.Checkable#isChecked()
*/
public boolean isChecked() {
return mIsChecked;
}
/*
* (non-Javadoc)
* @see android.view.View#onAttachedToWindow()
*/
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// Check if there is a valid GUI element that can visualize the current check-state.
if (mCheckedTextView != null) {
ViewParent p = getParent();
// Check if the parent of this list item is a ListView
if (p instanceof ListView) {
int choiceMode = ((ListView) p).getChoiceMode();
// Decide which check-state notation to visualize (check box, radio button or none).
switch (choiceMode) {
case ListView.CHOICE_MODE_MULTIPLE:
mCheckedTextView.setCheckMarkDrawable(mCheckDrawable);
break;
case ListView.CHOICE_MODE_SINGLE:
mCheckedTextView.setCheckMarkDrawable(mRadioDrawable);
break;
default:
mCheckedTextView.setCheckMarkDrawable(null);
break;
}
}
}
}
/*
* (non-Javadoc)
* @see android.view.View#onFinishInflate()
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mCheckedTextView = (CheckedTextView) findViewById(android.R.id.text1);
}
/*
* (non-Javadoc)
* @see android.widget.Checkable#setChecked(boolean)
*/
public void setChecked(boolean checked) {
mIsChecked = checked;
if (mCheckedTextView != null) {
mCheckedTextView.setChecked(mIsChecked);
}
}
/*
* (non-Javadoc)
* @see android.widget.Checkable#toggle()
*/
public void toggle() {
setChecked(!mIsChecked);
}
}
exampleListView.xml
NOTE! that you will automatically get check boxes if you set the android:choiceMode
attribute to "multipleChoice"
and radio buttons if you set it to "singleChoice"
, provided you use the above implementation.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:choiceMode="singleChoice"
android:id="@+id/myList" />
</LinearLayout>
Bonus: MyCustomAdapter::getView()
This one relies on a Cursor. You will, of course, implement it as you see fit for your needs.
private final class ViewHolder {
public ImageView iconView;
public TextView titleView;
public TextView descriptionView;
}
/*
* (non-Javadoc)
* @see android.widget.Adapter#getView(int, android.view.View, android.view.ViewGroup)
*/
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
// Only do something if the requested position exists within the Cursor.
if (mCursor.moveToPosition(position)) {
ViewHolder viewHolder;
view = convertView;
if (view == null) {
// Create and initialize a new view if not created already for this position.
view = mLayoutInflater.inflate(R.layout.listitem, null);
// Don't "find view by id" each and every time, but rather save a reference
// to them and associate the references with the list item itself by storing
// them in the list items "tag" attribute. When the view is re-used later on,
// you already have a reference to its views and don't need to find them
// again, which is a time-consuming operation.
viewHolder = new ViewHolder();
viewHolder.iconView = (ImageView) view.findViewById(R.id.myIcon);
viewHolder.titleView = (TextView) view.findViewById(R.id.myTitle);
viewHolder.descriptionView = (TextView) view.findViewById(R.id.myDescr);
view.setTag(viewHolder);
}
else {
// Get the references to the views for this, already existing list item.
viewHolder = (ViewHolder) view.getTag();
}
// Create a bitmap from the byte array in the database.
byte[] buffer = mCursor.getBlob(mIconColumnIndex);
Bitmap icon = null;
// Try to decode the byte array if it exists.
if (buffer != null) {
icon = BitmapFactory.decodeByteArray(buffer, 0, buffer.length);
}
// Update the views with new data.
viewHolder.iconView.setImageBitmap(icon);
String title = mCursor.getString(mTitleColumnIndex);
viewHolder.titleView.setText(title);
String description = mCursor.getString(mDescriptionColumnIndex);
viewHolder.descriptionView.setText(description);
}
// Return a view showing the correct data for the item at 'position'.
return view;
}
ORIGINAL ANSWER:
I can suggest this link:
http://tokudu.com/2010/android-checkable-linear-layout/
I myself have had great pleasure in it when I was in your exact position :-) If anything still is unclear, please feel free to specify your question and I will gladly try to help or assist with further code examples (just as mention earlier: I've been in your position just a few days ago).
I used dbm's answer and it worked fine for me except one thing:
You should initiate CheckedTextView
in onAttachedToWindow()
method, not in onFinishInflate().
This is because onAttachedToWindow()
called before onFinishInflate()
and using the solution above you will never see any drawable in CheckedTextView
—it is null when onAttachedToWindow()
called.