I want Radio Group implementation on GridView, so that only single item can be selected among the elements of grid.
Please help.
The purpose to restrict the select of element from the grid can be accomplished as follows:
1.Creation of Grid element.
<LinearLayout
android:id="#+id/item_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<ImageView
android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_launcher" />
<RadioButton
android:id="#+id/radiobtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Image" />
</LinearLayout>
2.Inflating this xml in the getView() method of customize adapter.
public class MyAdapter extends BaseAdapter {
Context mCtx;
int[] mImg;
LayoutInflater layoutInflater;
RadioGroup rgp;
private RadioButton mSelectedRB;
private int mSelectedPosition = -1;
public MyAdapter(Context context, int[] img) {
this.mCtx = context;
this.mImg = img;
rgp = new RadioGroup(context);
layoutInflater = (LayoutInflater) mCtx
.getSystemService(LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return mImg.length;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(final int position, View convertView,
ViewGroup parent) {
View view = convertView;
Holder holder;
if (view == null) {
view = layoutInflater.inflate(R.layout.element, null);
holder = new Holder();
holder.image = (ImageView) view.findViewById(R.id.imageView);
holder.radioButton = (RadioButton) view
.findViewById(R.id.radiobtn);
view.setTag(holder);
} else {
holder = (Holder) view.getTag();
}
holder.radioButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if ((position != mSelectedPosition && mSelectedRB != null)) {
mSelectedRB.setChecked(false);
}
mSelectedPosition = position;
mSelectedRB = (RadioButton) v;
}
});
if (mSelectedPosition != position) {
holder.radioButton.setChecked(false);
} else {
holder.radioButton.setChecked(true);
if (mSelectedRB != null && holder.radioButton != mSelectedRB) {
mSelectedRB = holder.radioButton;
}
}
return view;
}
}
private class Holder {
ImageView image;
RadioButton radioButton;
}
An alternative approach to this is to create your own subclass of RadioButton which has an extra XML attribute (such as group). This specifies (as a string) to which group the button belongs. In the subclass, you then ensure that within any particular group, only one radio button is selected.
You can do this as follows:
First create the file res/values/attrs.xml which contains something like the following:
<resources>
<declare-styleable name="GroupedRadioButton">
<attr name="group" format="string"/>
</declare-styleable>
</resources>
Then create your subclass, GroupedRadioButton:
public class GroupedRadioButton extends RadioButton {
public GroupedRadioButton(Context context, AttributeSet attrs) {
super(context, attrs);
processAttributes(context, attrs);
setOnClickListener(internalListener, true);
}
...
}
Once fleshed out (see below), you can then use this new class as follows in your layout files:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/com.example.app"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.example.app.GroupedRadioButton
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Option 1"
custom:group="group1" />
<com.example.app.GroupedRadioButton
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Option 2"
custom:group="group1" />
<com.example.app.GroupedRadioButton
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Option 3"
custom:group="group1" />
<com.example.app.GroupedRadioButton
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Option A"
custom:group="group2" />
<com.example.app.GroupedRadioButton
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Option B"
custom:group="group2" />
...
The radio buttons can be anywhere in your layout (e.g. in a GridView). Note the xmlns:custom tag is required since we are using a custom attribute.
The layout above will make options 1, 2 and 3 mutually exclusive and options A and B mutually exclusive.
This is achieved by keeping track (statically) of which GroupedRadioButton is currently selected within each group:
public class GroupedRadioButton extends RadioButton {
private static Map<String, WeakReference<GroupedRadioButton>> buttonMap;
static {
buttonMap = new HashMap<String, WeakReference<GroupedRadioButton>>();
}
...
}
Note that we have to be careful here to ensure that we don't keep strong references to the buttons otherwise they will never be garbage collected.
The processAttributes() method specified in the constructor above digs out the group attribute from the XML we specified and sets this as instance data:
private void processAttributes(Context context, AttributeSet attrs) {
TypedArray attributes = context.obtainStyledAttributes(attrs,
R.styleable.GroupedRadioButton);
int attributeCount = attributes.getIndexCount();
for (int i = 0; i < attributeCount; ++i) {
int attr = attributes.getIndex(i);
switch (attr) {
case R.styleable.GroupedRadioButton_group:
this.groupName = attributes.getString(attr);
break;
}
}
attributes.recycle();
}
We define the main OnClickListener for this class.
private OnClickListener internalListener = new OnClickListener() {
#Override
public void onClick(View view) {
processButtonClick(view);
}
};
which calls:
private void processButtonClick(View view) {
if (!(view instanceof GroupedRadioButton))
return;
GroupedRadioButton clickedButton = (GroupedRadioButton) view;
String groupName = clickedButton.groupName;
WeakReference<GroupedRadioButton> selectedButtonReference = buttonMap.get(groupName);
GroupedRadioButton selectedButton = selectedButtonReference == null ? null : selectedButtonReference.get();
if (selectedButton != clickedButton) {
if (selectedButton != null)
selectedButton.setChecked(false);
clickedButton.setChecked(true);
buttonMap.put(groupName, new WeakReference<GroupedRadioButton>(clickedButton));
}
if (externalListener != null)
externalListener.onClick(view);
}
This does two things. It ensures that it deselects the old group button before selecting the new one (assuming the old and new buttons are different). It then calls onClick() on an externalListener which is provided so that users of the class can add their own 'on-click' functionality.
The setOnClickListener() call in the constructor is to our own method as follows:
private void setOnClickListener(OnClickListener listener, boolean internal) {
if (internal)
super.setOnClickListener(internalListener);
else
this.externalListener = listener;
}
This sets the internalListener as the official OnClickListener and sets instance data as appropriate for the external listener. The View.setOnClickListener() method can then be overridden as follows:
#Override
public void setOnClickListener(OnClickListener listener) {
setOnClickListener(listener, false);
}
Apologies for the length of this answer but I hope it helps you and others trying to do the same thing. It would of course not be needed at all if a RadioGroup applied recursively to its children!
When you select an element of your grid, check that none of the other elements that you want to be in your radio group aren't selected and if they are deselect them...
Related
I have found similar questions, but can't find a specific answer that is up-to-date.
I'm using <preference-header>, as per 3.0+ settings design guidelines ( I target 4.1.2+) to build my headers; I want to set a custom layout to these headers. Note that I don't want to fall back to the old PreferenceScreen method as described here, because I don't support older Android version.
As far as I could research, this layout is held by a private member of the PreferenceActivity class, and it's retrieved with a styleable attribute that doesn't seem publicly accessible:
private int mPreferenceHeaderItemResId = 0;
...
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
...
TypedArray sa = obtainStyledAttributes(null,
com.android.internal.R.styleable.PreferenceActivity,
com.android.internal.R.attr.preferenceActivityStyle,
0);
...
mPreferenceHeaderItemResId = sa.getResourceId(
com.android.internal.R.styleable.PreferenceActivity_headerLayout,
com.android.internal.R.layout.preference_header_item);
...
}
This resource is then passed to a private Adapter to populate the header ListView.
Is there a way to pass a different layout resource?
UPDATE 14.4.2016: there was problem with recreate by savedInstanceState, but I found another similar solution from which I used setListAdapter method code (I modified the code below).
I solve this problem just too. I don't know if the following solution is correct, but was the fastest. Because PreferenceActivity is child of ListActivity, you can override setListAdapter method for using own adapter for header items. This is ugly hack, because setListAdapter method is called in PreferenceActivity.onCreate() with sets adapter parameter to new instance of HeaderAdapter, so following adjustment ignore this instance.
#Override
public void setListAdapter(ListAdapter adapter) {
int i, count;
if (mHeaders == null) {
mHeaders = new ArrayList<>();
// When the saved state provides the list of headers, onBuildHeaders is not called
// so we build it from the adapter given, then use our own adapter
count = adapter.getCount();
for (i = 0; i < count; ++i) {
mHeaders.add((Header) adapter.getItem(i));
}
}
super.setListAdapter(new CustomHeaderAdapter(this, mHeaders, R.layout.preference_header_item, true));
}
mHeaders property is defined as class member
private List<Header> mHeaders;
and is assigned in onBuildHeaders:
#Override
public void onBuildHeaders(List<Header> target) {
mHeaders = target;
loadHeadersFromResource(R.xml.preference_headers, target);
...
}
I copied and modified adapter inner class and layout from SDK source:
private static class CustomHeaderAdapter extends ArrayAdapter<Header> {
private static class HeaderViewHolder {
ImageView icon;
TextView title;
TextView summary;
}
private LayoutInflater mInflater;
private int mLayoutResId;
private boolean mRemoveIconIfEmpty;
public CustomHeaderAdapter(Context context, List<Header> objects, int layoutResId,
boolean removeIconBehavior) {
super(context, 0, objects);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mLayoutResId = layoutResId;
mRemoveIconIfEmpty = removeIconBehavior;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
HeaderViewHolder holder;
View view;
if (convertView == null) {
view = mInflater.inflate(mLayoutResId, parent, false);
holder = new HeaderViewHolder();
holder.icon = (ImageView) view.findViewById(R.id.icon);
holder.title = (TextView) view.findViewById(R.id.title);
holder.summary = (TextView) view.findViewById(R.id.summary);
view.setTag(holder);
} else {
view = convertView;
holder = (HeaderViewHolder) view.getTag();
}
// All view fields must be updated every time, because the view may be recycled
Header header = getItem(position);
if (mRemoveIconIfEmpty) {
if (header.iconRes == 0) {
holder.icon.setVisibility(View.GONE);
} else {
holder.icon.setVisibility(View.VISIBLE);
holder.icon.setImageResource(header.iconRes);
}
} else {
holder.icon.setImageResource(header.iconRes);
}
holder.title.setText(header.getTitle(getContext().getResources()));
CharSequence summary = header.getSummary(getContext().getResources());
if (!TextUtils.isEmpty(summary)) {
holder.summary.setVisibility(View.VISIBLE);
holder.summary.setText(summary);
} else {
holder.summary.setVisibility(View.GONE);
}
return view;
}
}
preference_header_item.xml with modified minHeight:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="64dp"
android:background="?android:attr/activatedBackgroundIndicator"
android:gravity="center_vertical"
android:paddingRight="?android:attr/scrollbarSize"
android:paddingEnd="?android:attr/scrollbarSize">
<ImageView
android:id="#+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dip"
android:layout_marginEnd="6dip"
android:layout_gravity="center" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="2dip"
android:layout_marginStart="2dip"
android:layout_marginRight="6dip"
android:layout_marginEnd="6dip"
android:layout_marginTop="6dip"
android:layout_marginBottom="6dip"
android:layout_weight="1">
<TextView android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
<TextView android:id="#+id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#android:id/title"
android:layout_alignLeft="#android:id/title"
android:layout_alignStart="#android:id/title"
android:textAppearance="?android:attr/textAppearanceSmall"
android:ellipsize="end"
android:maxLines="2" />
</RelativeLayout>
</LinearLayout>
I have implemented both a ViewHolder and a convertView in my listView.
My listView is populated by a custom adapter, with a list of bookings.
When I click on an item, an invisible layout slides in from right to left, to display buttons.
I can dismiss this overlaying layout by clicking on a dismiss button so that it gets hidden again.
On this overlaying layout, I have a delete Button, which enables me to delete the item.
So far so good.
When I erase an item the item disappears as expected, the adapter is then reloaded.
The item below takes the position of the deleted item, but remains invisible.
I know it is here, because I can still click on the item to trigger the overlaying View.
So the ovelaying view is visible but not the item. I have no idea why this is happening.
I suspect the ViewHolder to be responsible of this behaviour, but I can't find a solution.
Thank you for your help.
See video here : http://youtu.be/KBGEvbUq-V0
My Bookings Class :
public class BookingsListFragment extends Fragment {
private final String SHOP_NAME_KEY = "ShopName";
private final String SHOP_ADDRESS_KEY = "ShopAddress";
public static int mSelectedItem = -1;
private static ListView mBookingsListView;
private static BookingsListViewAdapter mBookingsListViewAdapter;
private static ArrayList<Booking> mBookings;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ImageLoader.getInstance().init(ImageLoaderConfiguration.createDefault(getActivity()));
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bookings_list_fragment, container, false);
configureListView(view);
return view;
}
#Override
public void onResume() {
super.onResume();
mSelectedItem = -1;
}
private void configureListView(View view) {
mBookings = BookingsHandler.getBookings();
mBookingsListView = (ListView) view.findViewById(R.id.bookingsListView);
mBookingsListViewAdapter = new BookingsListViewAdapter();
mBookingsListView.setAdapter(mBookingsListViewAdapter);
mBookingsListView.setTextFilterEnabled(true);
}
public static void updateBookingsListView(ArrayList<Booking> mBookingsList){
mBookings = mBookingsList;
mBookingsListViewAdapter.notifyDataSetChanged();
}
static class ViewHolder {
LinearLayout bookingItemLL;
RelativeLayout optionsOverlay;
TextView productName;
TextView price;
TextView shopName;
TextView endDate;
ImageView productImage;
LinearLayout placeholderLL;
Button cancelBooking;
Button displayDirections;
Button callShop;
ImageView discardOverlay;
}
private class BookingsListViewAdapter extends BaseAdapter {
private static final int TYPE_ITEM = 0;
private static final int TYPE_PLACEHOLDER = 1;
#Override
public int getCount() {
if (mBookings != null)
return mBookings.size();
else
return 1;
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
// Define a way to determine which layout to use
if (mBookings != null && mBookings.size() > 0)
return TYPE_ITEM;
else
return TYPE_PLACEHOLDER;
}
#Override
public int getViewTypeCount() {
return 2; // Number of different layouts
}
#Override
public View getView(final int position, View convertView, ViewGroup viewGroup) {
int type = getItemViewType(position);
final ViewHolder holder;
if(convertView == null) {
holder = new ViewHolder();
switch (type){
case TYPE_ITEM :
convertView = LayoutInflater.from(getActivity()).inflate(R.layout.bookings_item, null);
holder.bookingItemLL = (LinearLayout) convertView.findViewById(R.id.bookingItemLL);
holder.optionsOverlay = (RelativeLayout) convertView.findViewById(R.id.bookingOptionsOverlay);
holder.productName = (TextView) convertView.findViewById(R.id.bookingProductName);
holder.price = (TextView) convertView.findViewById(R.id.bookedProductPrice);
holder.shopName = (TextView) convertView.findViewById(R.id.bookingShopName);
holder.endDate = (TextView) convertView.findViewById(R.id.bookingEndDate);
holder.productImage = (ImageView) convertView.findViewById(R.id.bookedProductImage);
holder.displayDirections = (Button) convertView.findViewById(R.id.routeShop);
holder.cancelBooking = (Button) convertView.findViewById(R.id.cancelBooking);
holder.callShop = (Button) convertView.findViewById(R.id.callShop);
holder.discardOverlay = (ImageView) convertView.findViewById(R.id.discardOverlay);
break;
case TYPE_PLACEHOLDER :
convertView = LayoutInflater.from(getActivity()).inflate(R.layout.booking_placeholder, null);
holder.placeholderLL = (LinearLayout) convertView.findViewById(R.id.placeHolderLL);
break;
}
convertView.setTag(holder);
} else {
holder = (ViewHolder)convertView.getTag();
}
if(type == 0) {
if(position == mSelectedItem){
holder.optionsOverlay.setVisibility(View.VISIBLE);
configureOverlayButtons(holder);
}
holder.bookingItemLL.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(mSelectedItem != position && mSelectedItem != -1){
View item = mBookingsListView.getChildAt(mSelectedItem - mBookingsListView.getFirstVisiblePosition());
if(item != null){
RelativeLayout overlayOptions = (RelativeLayout) item.findViewById(R.id.bookingOptionsOverlay);
overlayOptions.setVisibility(View.GONE);
}
}
Animation slideInAnimation = AnimationUtils.loadAnimation(getActivity(), R.anim.booking_options_overlay_animation);
holder.optionsOverlay.startAnimation(slideInAnimation);
holder.optionsOverlay.setVisibility(View.VISIBLE);
mSelectedItem = position;
configureOverlayButtons(holder);
}
});
final Booking booking = mBookings.get(position);
holder.productName.setText(booking.getName().toUpperCase());
holder.price.setText("Prix lors de la réservation : " + String.format("%.2f", Float.valueOf(booking.getPrice())) + " €");
holder.shopName.setText(booking.getShopName());
holder.endDate.setText(booking.getEndDate());
holder.productImage.setScaleType(ImageView.ScaleType.CENTER_CROP);
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.product_placeholder)
.showImageOnFail(R.drawable.product_no_image_placeholder)
.cacheInMemory(true)
.cacheOnDisk(true)
.build();
ImageLoader imageLoader = ImageLoader.getInstance();
imageLoader.displayImage(BeeWylApiClient.getImageUrl(booking.getImageURL()),holder.productImage, options);
}
if(type == 1){
holder.placeholderLL.setLayoutParams(BeeWylHelper.getPlaceHolderSizeForFreeScreenSpace(getActivity(),0));
}
return convertView;
}
private void configureOverlayButtons(final ViewHolder holder){
holder.cancelBooking.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
AlertDialog.Builder ab = new AlertDialog.Builder(getActivity());
ab.setMessage("Annuler la réservation ?").setPositiveButton("Oui", dialogClickListener)
.setNegativeButton("Non", dialogClickListener).show();
}
});
holder.displayDirections.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
launchMapActivity();
}
});
holder.callShop.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
launchDialer();
}
});
holder.discardOverlay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Animation hideOverlayAnimation = AnimationUtils.loadAnimation(getActivity(), R.anim.booking_overlay_dismiss);
holder.optionsOverlay.startAnimation(hideOverlayAnimation);
holder.optionsOverlay.setVisibility(View.GONE);
holder.optionsOverlay.clearAnimation();
}
});
}
private void sendCancelBookingToAPI(String id_booking) throws JsonProcessingException {
BeeWylApiClient.cancelBooking(id_booking, new AsyncHttpResponseHandler() {
#Override
public void onSuccess(int i, Header[] headers, byte[] bytes) {
try {
Log.v("xdebug CANCEL", new String(bytes, "UTF_8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(int i, Header[] headers, byte[] bytes, Throwable throwable) {
Log.v("xdebug CANCEL ERROR", String.valueOf(throwable));
}
});
}
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
switch (which){
case DialogInterface.BUTTON_POSITIVE:
Animation hideOverlayAnimation = AnimationUtils.loadAnimation(getActivity(), R.anim.booking_overlay_dismiss);
mBookingsListView.getChildAt(mSelectedItem-mBookingsListView.getFirstVisiblePosition()).startAnimation(hideOverlayAnimation);
new Handler().postDelayed(new Runnable() {
public void run() {
try {
sendCancelBookingToAPI(mBookings.get(mSelectedItem).getId());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
mBookings.remove(mSelectedItem);
mSelectedItem = -1;
updateBookingsListView(mBookings);
}
}, hideOverlayAnimation.getDuration());
break;
case DialogInterface.BUTTON_NEGATIVE:
dialog.cancel();
break;
}
}
};
}
}
And the item inflated :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="5dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
>
<LinearLayout
android:id="#+id/bookingItemLL"
android:layout_width="match_parent"
android:layout_height="151dp"
android:orientation="horizontal"
android:weightSum="100"
android:background="#drawable/product_item_rectangle"
>
<ImageView
android:id="#+id/bookedProductImage"
android:layout_width="150dp"
android:layout_height="150dp"
android:background="#android:color/white"
android:src="#drawable/nivea"
/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_vertical"
>
<TextView
android:id="#+id/bookingProductName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="BRUME NIVEA"
android:textColor="#color/ProductsBlue"
android:textSize="16dp"
android:textStyle="bold"
/>
<TextView
android:id="#+id/bookedProductPrice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Prix lors de la réservation : 24,90€"
android:textSize="12dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="5dp"
android:textColor="#color/ProductsBlue" android:layout_gravity="left"
/>
<TextView
android:id="#+id/bookingShopName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="5dp"
android:text="Magasin"
android:textSize="12dp"
android:textColor="#color/ProductsBlue"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="5dp"
android:text="Réservé jusqu'au"
android:textSize="12dp"
android:textColor="#color/ProductsBlue" />
<TextView
android:id="#+id/bookingEndDate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="-"
android:textSize="12dp"
android:textColor="#color/ProductsBlue" />
</LinearLayout>
</LinearLayout>
<RelativeLayout android:id="#+id/bookingOptionsOverlay"
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="#EEFFFFFF"
android:visibility="gone">
<ImageView
android:id="#+id/discardOverlay"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:src="#drawable/ic_discard_booking_overlay"
android:padding="5dp"
/>
<Button android:id="#+id/callShop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="APPELER"
android:layout_weight="1"
android:background="#00000000"
android:drawableTop="#drawable/booking_call"
android:textColor="#color/ProductsBlue"
android:textSize="14dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:drawablePadding="20dp"
android:layout_marginLeft="20dp"
/>
<Button android:id="#+id/cancelBooking"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ANNULER"
android:layout_weight="1"
android:background="#00000000"
android:drawableTop="#drawable/booking_cancel"
android:textColor="#color/ProductsBlue"
android:textSize="14dp"
android:layout_centerInParent="true"
android:drawablePadding="20dp"
/>
<Button android:id="#+id/routeShop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ITINERAIRE"
android:layout_weight="1"
android:background="#00000000"
android:drawableTop="#drawable/booking_route"
android:textColor="#color/ProductsBlue"
android:textSize="14dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:drawablePadding="20dp"
android:layout_marginRight="20dp"
/>
</RelativeLayout>
</RelativeLayout>
Your problem comes from re-using the convertView.
When the previous item got a click the OnClickListener fired and in there the visibility for the item was set to GONE. Later on this same view got recycled and passed to getView() as the convertView. Because you are re-using it without resetting any changes that were made you are now working with a View for a new item that is not in a known state. You should make sure you undo any changes before using a convertView.
The quick fix is to not re-use the convertView that is passed into getView(). So, in your code where you check if you can re-use the convertView:
if(convertView == null)
Sabotage that check just to see if things start working:
if(true)
If that does the trick you will probably want to fix it properly.
In the else clause of the above check, you are getting the item holder from the tag. Also undo any changes that your OnClickListeners could have made. You want to start with a View for a new item in a known state. You should initialize it explicitly. For example:
if(convertView == null) {
// ... snipped all the initialization ...
} else {
holder = (ViewHolder)convertView.getTag();
convertView.setVisibility(View.VISIBLE);
}
Update
I have never used a 'heterogenous' adapter so I can't really answer why "the convertView is reusing the overlay View instead of my item's root View." The Android developer documentation for Adapter.getView() says about the convertView argument:
The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view. Heterogeneous lists can specify their number of view types, so that this View is always of the right type (see getViewTypeCount() and getItemViewType(int)).
The emphasized bit says that you cannot depend on the system to pass you a convertView of the right type, while the last sentence says the opposite (as I read it).
Basically, I don't know why it's not working. I guess in the test where you check if you must inflate a new view yourself
if(convertView == null)
you should also check if it is the right kind of view:
if(convertView == null || getItemViewTypeFromView(convertView) != type)
Where getItemViewTypeFromView() is something like this:
private int getItemViewTypeFromView(View view) {
switch (view.getId()) {
case R.id.item_layout_root:
return TYPE_ITEM;
case R.id.placeholder_layout_root:
return TYPE_PLACEHOLDER;
default:
throw new UnsupportedOperationException();
}
}
In the item and placeholder layouts, give the root elements an id so you distinguish between them. So something like this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/item_layout_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="5dp"
android:paddingLeft="5dp"
android:paddingRight="5dp" >
... snipped the elements that make up the body of the layout ...
</RelativeLayout>
I haven't tried the above, so I hope it works for you.
Good luck!
I have a problem with the Checkmark of my ListView row layout. The checkmark doesn't show up when the ListItem is clicked even though the ListView works (the interaction works). How can i fix this problem?
You will want to make you custom row layout that is checkable.
First you need to create a custom layout that implements Checkable:
public class CheckableLinearLayout extends LinearLayout implements Checkable {
private Checkable mCheckable;
public CheckableLinearLayout(Context context) {
this(context, null);
}
public CheckableLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean isChecked() {
return mCheckable == null ? false : mCheckable.isChecked();
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
// Find Checkable child
int childCount = getChildCount();
for (int i = 0; i < childCount; ++i) {
View v = getChildAt(i);
if (v instanceof Checkable) {
mCheckable = (Checkable) v;
break;
}
}
}
#Override
public void setChecked(boolean checked) {
if(mCheckable != null)
mCheckable.setChecked(checked);
}
#Override
public void toggle() {
if(mCheckable != null)
mCheckable.toggle();
}
}
After this layout is inflated it looks through it's children for one that is checkable (like a CheckedTextView or CheckBox), other than that it is quite simple.
Next use it in a layout:
<your.package.name.CheckableLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical" >
<TextView
android:id="#+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<CheckedTextView
android:id="#+id/text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checkMark="?android:attr/textCheckMark"
android:gravity="center_vertical"
android:paddingLeft="6dp"
android:paddingRight="6dp" />
</your.package.name.CheckableLinearLayout>
Notice that you cannot just use CheckableLinearLayout, since it is not a built-in Android View you need tell the compiler where it is. Save this as checkable_list_row.xml, for example.
Lastly use this new layout like you would with any other custom layout.
adapter = new MySimpleCursorAdapter(this, R.layout.checkable_list_row, cursor,
new String[] { Database.KEY_DATE , Database.KEY_NAME },
new int[] {R.id.text1, R.id.text2}, 0);
Hope that helps!
I am using a list view and an adapter for loading a list,each list item has a TextView,EditText and Image..I set the visibility of the arrow and the Edit text according to the position of the list row,everything works fine when I load the list for the first time...
But when I scroll through the list,visibility of the items keep changing...Kindly help me in this issue...The relevant codes has been attached...
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:orientation="horizontal" android:background="#FFFFFF">
<TextView android:layout_height="wrap_content" android:layout_width="0dip"
android:textSize="20dip" android:layout_weight="1"
android:id="#+id/textview_add_lot_list" android:textColor="#android:color/black"
android:paddingTop="10dip" android:paddingBottom="10dip"
android:paddingLeft="10dip"/>
<EditText android:layout_height="fill_parent" android:layout_width="0dip"
android:layout_weight="1" android:id="#+id/et_add_lot_list"
android:layout_gravity="center_vertical"/>
<ImageView android:layout_height="wrap_content" android:layout_width="wrap_content"
android:id="#+id/imageview_arrow_add_lot_list" android:layout_gravity="center_vertical"
android:visibility="invisible" android:src="#drawable/more_reviews_arrow"
android:paddingRight="10dip"/>
</LinearLayout>
Java code activity...
final ArrayList<String> listItems = new ArrayList<String>();
listItems.add("Parking name");
listItems.add("Address");
listItems.add("City");
listItems.add("State");
listItems.add("Zip");
listItems.add("Phone");
listItems.add("Web Address");
listItems.add(" ");
listItems.add("Parking Image");
listItems.add(" ");
listItems.add("Open Hours");
listItems.add(" ");
listItems.add("Web Reviews");
final AddParkingLotAdapter adapter = new AddParkingLotAdapter(mAppContext,0,listItems);
lv.setAdapter(adapter);
Java code...adapter
public class AddParkingLotAdapter extends ArrayAdapter<String> {
private ArrayList<String> mStrings;
private LayoutInflater mInflater;
private AppContext mContext;
private static int NON_EMPTY_ROW = 1;
private static int EMPTY_ROW = 0;
public AddParkingLotAdapter(Context context, int resId, List<String> strings) {
super(context, resId,strings);
mStrings = (ArrayList<String>) strings;
mContext = (AppContext) context;
mInflater = LayoutInflater.from(context);
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getCount() {
return mStrings.size();
}
#Override
public String getItem(int position) {
return mStrings.get(position);
}
#Override
public int getItemViewType(int position) {
if(position==7||position==9||position==11){
return EMPTY_ROW;
}else{
return NON_EMPTY_ROW;
}
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView itemTextView = null;
//different inflations for different type rows..
if(getItemViewType(position) == EMPTY_ROW){
if (convertView == null) {
convertView = mInflater.inflate(R.layout.review_empty_row, null);
}
}else if(getItemViewType(position) == NON_EMPTY_ROW){
if (convertView == null) {
convertView = mInflater.inflate(R.layout.add_parkinglist_item, null);
}
itemTextView = (TextView) convertView.findViewById(R.id.textview_add_lot_list);
itemTextView.setText(mStrings.get(position));
if (position==3||position==8||position==10||position==12){
ImageView itemImageView = (ImageView)convertView.findViewById(R.id.imageview_arrow_add_lot_list);
itemImageView.setVisibility(View.VISIBLE);
EditText editText = (EditText)convertView.findViewById(R.id.et_add_lot_list);
editText.setVisibility(View.INVISIBLE);
}
}
return convertView;
}
}
In this code:
if (position==3||position==8||position==10||position==12){
ImageView itemImageView = (ImageView)convertView.findViewById(R.id.imageview_arrow_add_lot_list);
itemImageView.setVisibility(View.VISIBLE);
EditText editText = (EditText)convertView.findViewById(R.id.et_add_lot_list);
editText.setVisibility(View.INVISIBLE);
}
you've got no else clause. That means that if position is 0,1,2,4,5 or 6 you don't explicitly set the visibility of the views and so the visibility will be whatever it was set to when the views were recycled. If convertView is non-null, you always need to reset the visibility of any items whose visibility may be been modified earlier.
I have a ListView with EditText inside.
Actually, when i touch an element of the Listview, the EditText have the focus and the keyboard appeared. Good.
The problem is i wanna do something on this EditText throught the listView's onItemClickListener, but seems that my code never enter in this method.
I try some setDescendantFocusability to my Listview but don't solve the problem.
Thanks a lot.
public class NoteAdapter extends BaseAdapter {
private ArrayList<String> notes;
private LayoutInflater inflater;
private Context context;
public NoteAdapter(Context context, ArrayList<String> notes) {
inflater = LayoutInflater.from(context);
this.notes = notes;
this.context = context;
}
public int getCount() {
// TODO Auto-generated method stub
return notes.size();
}
public Object getItem(int position) {
// TODO Auto-generated method stub
return notes.get(position);
}
public long getItemId(int id) {
// TODO Auto-generated method stub
return id;
}
private class ViewHolder {
EditText note;
}
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder holder;
if(convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.listenote, null);
holder.note = (EditText)convertView.findViewById(R.id.note);
convertView.setTag(holder);
}else {
holder= (ViewHolder) convertView.getTag();
}
holder.note.setText(notes.get(position));
return convertView;
}
}
my main activity
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
notes = new ArrayList<String>();
for(int i=0; i< 10; i++)
notes.add("note"+i);
EditTextSelected = null;
adapter = new NoteAdapter(this, notes);
lv1 = ((ListView)findViewById(R.id.listeNote));
lv1.setAdapter(adapter);
lv1.setClickable(true);
lv1.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> a, View v, int position, long id) {
Toast t = Toast.makeText(FastItActivity.this, "hello", 200);
t.show();
}
});
listenote.xml
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
android:id="#+id/widget1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<EditText
android:id="#+id/note"
android:textColor="#color/black"
android:textSize="12dp"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:padding="5dp"
android:inputType="textMultiLine"
android:scrollHorizontally="false"
android:gravity="top|left"
android:ems="10"
android:layout_margin="10dp"
android:background="#drawable/fond_note"
/>
</TableLayout>
main.xml
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
android:id="#+id/widget1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#drawable/wooden_top"
>
<ListView
android:id="#+id/listeNote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:background="#android:color/transparent"
android:cacheColorHint="#00000000"
android:isScrollContainer="false"
android:divider="#00000000"
>
</ListView>
</TableLayout>
Move your modifications from ListView's onItemClickListener to your EditText's onClickListener
In NoteAdapter's getView:
holder.note = (EditText)convertView.findViewById(R.id.note);
holder.note.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//do something
}
});
or try this:
holder.note.setOnFocusListener(new View.OnFocusListener(){
#Override
public void onFocus(){
//do something
}
}
getView method has position parameter so you'll be able to distinguish what EditText was clicked (if you need different actions with different EditTexts)
This article is pretty long but towards the middle/end he demonstrates an interactive listview which is exactly what you need. http://www.vogella.de/articles/AndroidListView/article.html
If you could post some code that would help.
I was trying to solve a similar problem: which item in a list was selected when you embed several views in list item?!
I refuse to create a new listener for each item in the list. I can't imagine that would scale well on such a resource constrained platform. But, I found you can solve this problem by specializing EditText to set and retrieve the selected index in onClick.
Define your specialization:
package userInterface;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.EditText;
public class IndexedEditText extends EditText {
public int listIndex;
public IndexedEditText(Context context) {
super(context);
}
public IndexedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public IndexedEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
}
Replace your EditText for your specialized class in the list item XML declaration. Be sure to get the path to your new class right (in my case it's userInterface.IndexedEditText).
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<userInterface.IndexedEditText
android:id="#+id/et_first_item"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:hint="#string/select"
android:inputType="none" />
<!-- other views -->
</RelativeLayout>
Set listIndex in getView, and set your OnClickListener for each IndexedEditText instance:
public abstract class EditTextPairArrayAdapter <T> extends ArrayAdapter<T> {
LayoutInflater inflater;
static class ViewHolder {
private WeakReference<IndexedEditText> name;
private WeakReference<EditText> notes;
public ViewHolder(IndexedEditText tv, EditText et) {
name = new WeakReference<IndexedEditText>(tv);
notes = new WeakReference<EditText>(et);
}
}
int textViewId;
int editTextId;
int listItemId;
List<T> list = null;
WeakReference<Context> contextRef;
//context is Activity that instantiates this array adapter
//resourceId is the layout xml ID for your special row
//textViewResourceId is any TextView ID in your special row xml def
//editTextResourceId means nothing in this context
//objects is the initial list of objects to present in UI
public EditTextPairArrayAdapter(Context context, int resourceId, int textViewResourceId, int editTextResourceId, List<T> objects) {
super(context, resourceId, textViewResourceId, objects);
this.listItemId = resourceId;
this.textViewId = textViewResourceId;
this.editTextId= editTextResourceId;
this.list = objects;
this.contextRef = new WeakReference<Context>(context);
}
#Override
public View getView(int position, View convertView, ViewGroup viewGroup) {
View view = null;
ViewHolder viewHolder = null;
if (convertView == null) {
if(inflater == null)
inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(listItemId, null);
IndexedEditText text = (IndexedEditText)view.findViewById(textViewId);
EditText notes = (EditText) view.findViewById(editTextId);
text.listIndex = position;
//Special sauce
if(contextRef != null && contextRef.get() != null && (contextRef.get() instanceof View.OnClickListener)) {
text.setOnClickListener((View.OnClickListener) contextRef.get());
}
viewHolder = new ViewHolder(text, notes);
view.setTag(viewHolder);
}
else {
view = convertView;
viewHolder = (ViewHolder) convertView.getTag();
}
Titem = this.getItem(position);
if(item != null) {
//special sauce
}
return view;
}
//add abstract methods for implementations to define special sauce
}
Finally, in your Activity that implements OnClickListener:
public void onClick(View v) {
if(v instanceof IndexedEditText) {
Object obj = myList.get(((IndexedEditText)v).listIndex);
//do stuff with obj
}
}
The solution I found is to superimpose an EditText with a TextView and alternate gone/visible on both so that the EditText stops inducing bugs when hidden.