I am trying to simulate a ListView in a LinearLayout in order to show three rows with identical layout. This single View is composed by a ratingbar and a content. It is very strange, but all ratingBars receive the last assigned value. First of all this is my custom Component that extends LinearLayout just adding these two methods:
public void setElements(List<Item> elements) {
removeAllViews();
for (int i = 0; i < elements.size() && i < 3; i++) {
View vi = buildElementView(elements.get(i));
vi.setId(i);
addView(vi);
}
}
private View buildElementView(Item itemElement) {
View view = inflater.inflate(R.layout.element_list_item, null, true);
// set values
View header = view.findViewById(R.id.header);
RatingBar ratingInItem = (RatingBar) header.findViewById(R.id.ratingBarInItem);
TextView content = (TextView) view.findViewById(R.id.content);
content.setText(itemElement.getContent());
ratingInItem.setRating(itemElement.getRating());
return view;
}
and this is the layout I am inflating:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.weorder.client"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#color/transparent"
android:minHeight="60.0dp"
android:orientation="vertical"
android:paddingBottom="8.0dp"
android:paddingLeft="5.0dp"
android:paddingRight="5.0dp"
android:paddingTop="8.0dp" >
<RelativeLayout
android:id="#+id/header"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<RatingBar
android:id="#+id/ratingBarInItem"
style="#style/RatingBarSm"
android:isIndicator="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:numStars="5"
android:stepSize="0.1" />
</RelativeLayout>
<TextView
android:id="#+id/content"
style="#style/Content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:layout_marginTop="3dp"
android:textColor="#color/dark_brown"
android:textSize="#dimen/text_size_small" />
</LinearLayout>
I call the setElements method inside onActivityCreated() in the fragment. It works well when the fragment start, but when I try to rotate the phone, the content of items changes properly but the ratingbar gets the last value (if there are 3 elements with rating 1,2 and 3, all ratingbars have 3 stars). Is it a bug?
EDIT: this is my onSaveInstanceMethod:
#Override
public void onSaveInstanceState(Bundle outState) {
if (elements != null) {
outState.putSerializable("elements", elements);
}
super.onSaveInstanceState(outState);
}
Thanks
I think you are using the findViewById() with the wrong view which is causing issues.
Here is a sample based on what you are doing that works for me.
LinearLayout ll = (LinearLayout)findViewById(R.id.ll);
ViewGroup parentGroup = parentGroup = (ViewGroup)ll;
for (int i = 0; i < 3; i++)
{
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.element_list_item, null, true);
RatingBar rBar = (RatingBar)view.findViewById(R.id.ratingBar1);
rBar.setProgress(i);
parentGroup.addView(view);
}
The results should show three rating bars with 0, 1, and 2 stars depending on how you set up the rating bar.
I attach the new view to the view parent three times. The parent is my linearlayout and the new view is the rating bar (or whatever you choose).
For saving in fragments use
#Override
public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
getFragmentManager().putFragment(outState, Content.class.getName(), this);
}
then you can get your arguments in the onCreateView and restore your data (if necessary)
Bundle extras = this.getArguments();
if(extras != null)
{
//set arguments here
}
It works well when the fragment start, but when I try to rotate the phone, the content of items changes properly but the ratingbar gets the last value (if there are 3 elements with rating 1,2 and 3, all ratingbars have 3 stars). Is it a bug?
This is not a bug - this is how Android works.
Why is this happening?
By default, the system will save and restore the state of views that have IDs and it uses those IDs to maintain the internal map of state. Because you are using the same layout in the LinearLayout, each child has a RatingBar with the ID ratingBarInItem. When the system saves your view's state, it saves the state of the first bar, then overwrites it with the second bar, then overwrites that with the third bar. Then, when you restore state, it restores every view with the ID ratingBarInItem to what it has in the state map - which will be the last value saved, the value from the 3rd item.
How do you fix this?
You have two options.
Manually save and restore the state of the views yourself.
You show that you are already saving the "elements" that are in the view. Well, you could update onRestoreInstanceState to read those elements back then use them to re-update the child views (i.e., just call setElements again).
Give each rating bar a unique ID.
After you inflate a child view to add to the layout, use generateViewID to create a new, unique ID you can set on each RatingBar.
RatingBar ratingInItem = (RatingBar) header.findViewById(R.id.ratingBarInItem);
ratingInItem.setID(View.generateViewID());
Then you don't have to implement saving or restoring state because, now that each view has a unique ID, the default system behavior will work.
Hope that helps!
Related
There is a Fragment named FragmentA that has a RelativeLayout with an ImageView behind it. (Say 4)Textviews are dynamically added to the rlParentView This layout resides inside a Fragment layout.
The Textviews are draggable inside the parent layout.
Another Fragment is loaded in the same activity and when FragmentA is reloaded, now the dynamically added textviews are lost so how can I retain the dynamically added TextViews with their text and other bounds.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.85"
android:layout_margin="5dp"
android:id="#+id/rlParentView">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/background_image" />
</RelativeLayout>
Dragable TextView are added dynamically like this:
View inflateLayout=mInflater.inflate(R.layout.text_drag_layout,mParentContainer,false);
TextView draggableView= (TextView) inflateLayout.findViewById(R.id.draggableView);
rlParentView.addView(inflateLayout);
draggableView.setText(Some_Text_here);
OnDragTouchListener listener=new OnDragTouchListener(draggableView, rlParentView,
new OnDragTouchListener.OnDragActionListener() {
#Override
public void onDragStart(View view) {
}
#Override
public void onDragEnd(View view) {
}
}
);
draggableView.setOnTouchListener(listener);
I haven't tried it, but here's an idea:
In onSaveInstanceState() of Fragment A, save the positions (and whetever else) of the TextViews inside their parent. Something like:
class ViewPositionInfo implements Serializable {
String text;
int xPositionInParent;
int yPositionInParent;
// ... other variables you need to store
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
List<ViewPositionInfo> positionInfos = new ArrayList<>();
// iterate over the views and add them inside the list
outState.putExtra("positions", positionInfos);
}
Then, when the fragment is re-created, read this information in onCreateView(), create TextViews with those attributes and add them to the parent layout.
The solution above has a drawback. When rotating the screen, the width / height will be different than before, so saving x and y positions of the the text views inside their parent might not be a good idea. You might need to save a relative position, i.e. a percentage. But first things first - make the simple case work (as explained above) and then think about this one.
I'm trying to write a custom compound view composed by a TextView and an EditText, _compound_view.xml_:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/compoundText"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="#+id/textLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Label" />
<EditText
android:id="#+id/textEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="enter text here" >
</EditText>
and this is the class extending LinearLayout:
public class CompoundView extends LinearLayout {
public CompoundView(Context context, AttributeSet attrs) {
super(context, attrs);
readAttributes(context, attrs);
init(context);
}
public CompoundView(Context context) {
super(context);
init(context);
}
private void init(Context c) {
final LayoutInflater inflater = (LayoutInflater) c
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.compound_view, this);
}
}
Now, if I use 2 of these View in my _activity_main.xml_:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<it.moondroid.compoundview.example.CompoundView
android:id="#+id/compoundview1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true" />
<it.moondroid.compoundview.example.CompoundView
android:id="#+id/compoundview2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/compoundview1" />
</RelativeLayout>
and in the Activity code I only inflate the RelativeLayout, without managing onSaveInstanceState:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
then when I write something in the 2nd EditText and I rotate my device, the same text appears in the EditText of the first custom View.
Why is happening this behaviour?
EDIT:
I solved the issue by removing android:id and using android:tag for the EditText in compound_view.xml, then managing the saving of the EditText state in CompoundView class:
#Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable("instanceState", super.onSaveInstanceState());
bundle.putString("currentEdit", mEditText.getText().toString());
bundle.putBoolean("isFocused", mEditText.hasFocus());
return bundle;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
mEditText.setText(bundle.getString("currentEdit"));
if (bundle.getBoolean("isFocused")) {
mEditText.requestFocus();
}
super.onRestoreInstanceState(bundle.getParcelable("instanceState"));
return;
}
super.onRestoreInstanceState(state);
}
You need to disable SaveEnabled property of EditText using android:saveEnabled="false"
In your custom view, you are inflating layout from XML which has ID defined. Android OS has default functionality to save and restore the state of the view if the view has ID defined.
It means that it will save the text of the EditText when Activity gets paused and restore automatically when Activity gets restored. In your case, you are using this custom view multiple times and that is inflating the same layout so your all EditText have the same ID. Now when Activity will get pause Android will retrieve the value of the EditText and will save against their ID but as you have the same ID for each EditText, values will get override and so it will restore same value in all your EditText.
I'll start off by saying that I haven't confirmed this... but I experienced the same issues when using a compound view, similar to what you were doing.
I think the root of the problem is how Android automatically saves the state of EditText controls, and I think it saves it by "id". So if you have multiple controls in your xml with same "id", then when it saves state, and then restores state, all controls with the same id get the same value. You can try this by adding 2 EditText contols to you normal xml and give them the same android:id value and see if they end up getting the same value.
In your case, you can try to NOT use ids in the compound view and rather find the elements another way, either by tag (View.findViewWithTag), or by name, and see if that makes a difference.
In my case, I solved the problem by doing the latter.
I had the same issue, This is how I made it to work. First need to set false for saveEnabled for editText. We can keep android:id in our layout.
<EditText
android:id="#+id/editText"
android:saveEnabled="false"
Then override below methods in your compound view and manage state by your own. Feel free to ask working example if needed.
#Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable("state", super.onSaveInstanceState());
String text = editText.getText().toString();
bundle.putString("text", text);
return bundle;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
String text = bundle.getString("text");
state = bundle.getParcelable("state");
editText.setText(text);
}
super.onRestoreInstanceState(state);
}
Take a look at my comment in your question and also make sure that you're getting correctly the references to your views.
I'm using your code like this:
CompoundView cv1 = (CompoundView) findViewById(R.id.compoundview1);
TextView tv1 = (TextView) cv1.findViewById(R.id.textEdit);
CompoundView cv2 = (CompoundView) findViewById(R.id.compoundview2);
TextView tv2 = (TextView) cv2.findViewById(R.id.textEdit);
In case you have more complex compound view with many child view, you can consider overriding the dispatchSaveInstanceState of your most outer ViewGroup class and don't call the super implementation.
Like this:
#Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
//If you don't call super.dispatchRestoreInstanceState(container) here,
//no child view gets its state saved
}
I use this, because in my case I have hierarchy of many different compound views that have common super class in which I did this override. (I kept forgetting to set the SaveEnabled attribute to false for new layouts.) However there are many caveats, for example you need to manually save focused view and then request its focus, so your app doesn't behave oddly when screen is rotated.
EDIT:
If you actually need to save state of your compound view, overriding dispatchSaveInstanceState with an empty body will cause onSave/RestoreInstanceState not being called. If you want to use them and still not save state of any of the child views, you need to call super.dispatchFreezeSelfOnly(container) in your override.
More on this here: http://charlesharley.com/2012/programming/views-saving-instance-state-in-android
The issue is happening because of the id field on compound_view.xml
From your code, I just noticed that you inflated compound_view layout file in CompoundView class.
As soon as you create compound_view.xml and put android:id="#+id/textLabel" and android:id="#+id/textEdit" id in your layout xml file, android studio automatically create those ids into int values in R class for single time.
So, when you put CompoundView twice time in your activity_main.xml layout file, you just creating two instance of CompoundView but, both instances textEdit and textLabel have only 1 address location for each one. So, they are pointing to same address locations which are declared in R class.
That's why, whenever you change textEdit or textLabel text programatically, they also change other textEdit or textLabel which are presented in both of your CompoundView
I would like to emphasize a great article, which opened my eyes. It is based on reimplementing onSaveInstanceState() and onRestoreInstanceState(state: Parcelable?).
The advantage of this is that you can use the same compound view multiple times in the same layout (no duplicate ids problem).
In case someone has troubles with incorrect focus being restored after screen rotation, which occurs due to the shared ids of inner views, you can control which id is saved as focused view by overriding findFocus method like this:
override fun findFocus(): View {
if (focusedChild != null) {
return this
}
return super.findFocus()
}
Then the focus gets restored to your compound view, however you should handle the requestFocus call, so the proper child view gets focus upon restoration.
I got the same annoying problem and solved it with another solution than the ones suggested. When the custom view is inflated, I don't find them with IDs or tags, I get them using the getChildAt method. I did not have to save the instance state.
Here is an example:
Custom XML to inflate (custom_view.xml):
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"/>
</merge>
You then just get the views this way during the view initialization:
private void initView(Context context) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.custom_view, this, true);
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER);
TextView mTitleTV = (TextView) getChildAt(0);
EditText mContentET = (EditText) getChildAt(1);
}
IMPORTANT: you must remove all IDs from the XML
I'm using a Spinner with a custom adapter class called AlgorithmAdapter and in principle everything's working fine, meaning the spinner popup appears and all containing views are properly inflated. However, what I'm not able to find out is how I "tell" the spinner when the selection is made. Of course I know about setSelection(), but that doesn't dispose the popup and return focus to the Activity.
So here's some of the relevant code:
In my activity, I create my adapter like this:
SpinnerAdapter spinnerAdapter = new AlgorithmAdapter(
this,
algorithmSpinner,
R.layout.algo_spinner_item_detail,
res.getStringArray(R.array.anaglyph_algorithm_titles),
res.getStringArray(R.array.anaglyph_algorithm_descriptions),
res.obtainTypedArray(R.array.anaglyph_algorithm_icons),
algorithmSpinner.getSelectedItemPosition()
);
algorithmSpinner.setAdapter(spinnerAdapter);
The constructor of AgorithmAdapter has the following signature:
public AlgorithmAdapter(Context context, Spinner spinner, int viewResourceId, String[] titles,
String[] descriptions, TypedArray icons, int selectedPosition) {
The getDropDownView() method of AlgorithmAdapter:
#Override
public View getDropDownView(final int position, View convertView, ViewGroup parent) {
if(convertView == null) {
LayoutInflater inflater = LayoutInflater.from(context);
convertView = inflater.inflate(viewResourceId, parent, false);
}
final ImageView icon = (ImageView) convertView.findViewById(R.id.algoItemIcon);
final TextView title = (TextView) convertView.findViewById(R.id.algoItemTitle);
final TextView description = (TextView) convertView.findViewById(R.id.algoItemDescription);
final RadioButton radio = (RadioButton) convertView.findViewById(R.id.algoItemRadio);
icon.setImageDrawable(icons.getDrawable(position));
title.setText(titles[position]);
description.setText(descriptions[position]);
radioGroup.add(radio);
/* it's essential to uncheck in case the positions do not match, because
* the system reuses views that could still have a checked radio button */
radio.setChecked(position == selectedPosition);
convertView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
// uncheck every other radio button
for(RadioButton each : radioGroup)
each.setChecked(false);
// inform adapter and user that this button is selected now
radio.setChecked(true);
selectedPosition = position;
spinner.setSelection(position);
}
});
return convertView;
}
where radioGroup is a list of all RadioButtons from all drop down views in order to manage which one is currently checked and which ones have to be unchecked therefore.
That all works very well except for the fact that the popup doesn't close on setSelection() (which itself is working though).
I also tried to attach an OnItemClickListener to the Spinner and manage the selection stuff in there, but that throws an exception saying setOnItemClickListener cannot be used with a spinner, which is really annoying since it feels as if the framework stands in my way instead of helping me at this point. But anyway, that's the reason I have to pass the Spinner through to the adapter which I would be glad I had a different solution for…
So the only thing I'm missing is how I "close" or "dispose" the Spinners drop down. Could anybody help me with that? Is this even anywhere near the way do to it? It cannot be that difficult since writing custom adapters for Spinners is a pretty basic task.
EDIT: No idea if this helps, but maybe someone finds an error in the layout file of my drop down view item. algo_spinner_item_detail.xml:
<?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="match_parent"
android:padding="10dp"
android:background="#android:drawable/list_selector_background" >
<ImageView
android:id="#+id/algoItemIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:contentDescription="#string/algoIconDescription"
android:layout_marginRight="8dp" />
<RadioButton
android:id="#+id/algoItemRadio"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_marginLeft="10dp"
android:layout_centerVertical="false" />
<TextView
android:id="#+id/algoItemTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="#id/algoItemIcon"
android:layout_alignTop="#id/algoItemIcon"
android:textStyle="bold"
android:layout_marginBottom="1dp" />
<TextView
android:id="#+id/algoItemDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/algoItemTitle"
android:layout_alignLeft="#id/algoItemTitle"
android:layout_toLeftOf="#id/algoItemRadio" />
</RelativeLayout>
How the heck do you close/dispose the f***ing Spinner drop down after you selected an item?
Here's what did the trick for me (WARNING: don't show this code to your kids, you'd certainly go to hell for it):
In the onClick method of my convertView I put this line of code:
((View) parent.getParent().getParent().getParent().getParent()).setVisibility(View.GONE);
which not only gets rid of the LinearLayout in which the convertViews are contained, but also makes the com.android.internal.policy.impl.PhoneWindow$DecorView invisible, that was there to darken the Activity screen while a dialog/drop down is shown.
I'm still after a better solution.
I have a GridView whose elements are based on a FrameLayout containing an ImageView and a CheckedTextView. The xml file for the FrameLayout is as follows:
<FrameLayout
xmlns:android = "http://schemas.android.com/apk/res/android"
android:id="#+id/gridImage"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:gravity="center_horizontal">
<ImageView
android:id="#+id/image"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:adjustViewBounds="false">
</ImageView>
<CheckedTextView
android:id="#+id/imageTick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_gravity="center_horizontal|bottom"
android:checkMark="#drawable/icon"
android:checked="false"
android:visibility="invisible"
>
</CheckedTextView>
</FrameLayout>
EDIT: This is the adapter's getView() method i use for each element:
public View getView(int position, View convertView, ViewGroup parent) {
Log.d("getView()", position+"");
View v;
ImageView imageView;
if(convertView == null) {
v = LayoutInflater.from(mContext).inflate(R.layout.photo_text_view, null);
v.setLayoutParams(new GridView.LayoutParams(100,100));
}
else
{
v = convertView;
}
imageView = (ImageView) v.findViewById(R.id.image);
imageView.setImageBitmap(mThumbsIds.get().get(position));
v.setPadding(8, 8, 8, 8);
return v;
}
In my activity class, I load a context menu and once i select an option i make the CheckedTextView visible for that GridView element like so :
GridView gridView = (GridView) findViewById(R.id.gridview);
View selected =gridView.getChildAt(position);
CheckedTextView selectedCheck = (CheckedTextView)selected.findViewById(R.id.imageTick);
selectedCheck.setChecked(true);
selectedCheck.setVisibility(View.VISIBLE);
So two things can potentially happen from this point:
1) Lets say I picked the element at position 0, the CheckedTextView becomes visible, but when i scroll down my GridView, another element (e.g. position 17) has its CheckedTextView visible aswell. This continues on random elements as i scroll down towards the bottom.
2) If i pick an element towards the bottom, scroll back up to the top, and run one of methods to make all CheckedTextView's invisible, a NullPointerException is thrown for the element at the bottom. This happens at this point: View selected =gridView.getChildAt(position); where selected becomes null.
Whats going on here? Why do these random CheckedTextView's become visible and why do i get exceptions?
The problems lie in the way you track checked items:
GridView gridView = (GridView) findViewById(R.id.gridview);
View selected =gridView.getChildAt(position);
CheckedTextView selectedCheck = (CheckedTextView)selected.findViewById(R.id.imageTick);
selectedCheck.setChecked(true);
selectedCheck.setVisibility(View.VISIBLE);
1) getChildAt will not give you the correct view if you've scrolled into content and you're indexing by adapter position. When referencing an active view in a GridView or ListView you want to do something like this:
final int index = position - gridView.getFirstVisiblePosition();
View selected = gridView.getChildAt(index);
The reason is that your GridView only keeps child views for adapter items that it is currently displaying. It may only have child views for elements 4 to 23 if elements 0 to 3 were scrolled off of the top earlier.
This is why you're getting exceptions when you do View selected =gridView.getChildAt(position); a view for that position does not actually exist when it's off-screen, so getChildAt returns null.
2) When a ListView or GridView has its content change or it otherwise needs to re-layout its child views, it does so by re-binding existing views using the convertView parameter to your adapter. Your adapter never adjusts the checked state of your item views, so a checked view can be reused for an item that should be unchecked later.
To solve this you should have your data model that your adapter presents track the checked state of your items rather than relying on the item views to do it. This means that your adapter should always verify/set the checked state of the item in getView. It may look something like this:
imageView = (ImageView) v.findViewById(R.id.image);
imageView.setImageBitmap(mThumbsIds.get().get(position));
CheckedTextView checkView = (CheckedTextView) v.findViewById(R.id.imageTick);
if (mData.isItemChecked(position)) {
checkView.setChecked(true);
checkView.setVisibility(View.VISIBLE);
} else {
checkView.setVisibility(View.GONE);
}
Then when your option is selected that should change the checked state of an item, instead of the code snippet above do something like:
data.setItemChecked(position, true);
adapter.notifyDataSetChanged();
notifyDataSetChanged() will tell the GridView that it should re-bind the item views, which will have your getView method fix up the checked states properly.
I have a ListView with an ArrayList adapter. The rows are not very complex (an Image on the left, a LinearLayout with TextViews inside, and a CheckBox on the right ... the layout is copied in below.) The goal is to have a QuickAction bar come up if the user clicks on the image or text, and have the CheckBox change state if the user clicks on the CheckBox. I have each part working independently, but not when they're together in the layout - somehow, I'm losing the onItemClick event.
The "QuickAction" bar is activated by OnItemClickListener(), and it works fine - unless I have the CheckBox in the layout, in which case the CheckBox works fine (using onClick()) but the onItemClickListener is never fired if the user clicks in the row but outside the CheckBox. The layout (minus some style stuff) is:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/vw1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView android:id="#+id/img"
android:layout_alignParentLeft="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<CheckBox android:id="#+id/ckb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_gravity="right"
android:checked="false" />
<!-- stack text in middle section of the row -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView android:id="#+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content""/>
<TextView android:id="#+id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</RelativeLayout>
The code behind it isn't complicated:
public class ListGroupsActivity extends BaseActivityForList {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list);
// add QuickAction bar
ListView lv = getListView();
lv.setOnItemClickListener(new QAListener(this));
}
}
//inner classes of the ListGroupsActivity
class CbOnClickListener implements OnClickListener {
// ...
// test the checkbox isChecked() and keep state in 'selectedItems' array
class GroupListAdapter extends ArrayAdapter<Group> {
super(/*...*/)
CbOnClickListener cblistener = null; // common listener for all the CheckBoxes
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// here do normal testing for convertView being null etc
// heres the view inflation from the layout into the ViewHolder
holder.tv1 = (TextView) convertView.findViewById(R.id.text1);
holder.tv2 = (TextView) convertView.findViewById(R.id.text2);
holder.img = (ImageView) convertView.findViewById(R.id.img);
holder.ckb = (CheckBox) convertView.findViewById(R.id.ckb);
Group g = groups.get(position); // this is the data obj for the row
holder.tv1.setText(g.getName());
holder.tv2.setText("child groups");
Bitmap bm = BitmapFactory.decodeFile(g.getIconUri());
holder.img.setImageBitmap(bm);
Integer key = (Integer) g.getId();
holder.ckb.setChecked(selectedItems.contains(key));
holder.ckb.setOnClickListener(cblistener); // hook onClick to cblistener
return convertView;
}
The QAListener is in a superclass for all my list activities:
public class BaseActivityForList extends
OrmLiteBaseListActivity<DatabaseHelper> {
// QAListener inner class, constructs new quick action bar when item clicked
class QAListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// has three ActionItem instances, 'copyAction', 'delAction', 'editAction'
ActionItem copyAction = new ActionItem();
copyAction.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// ... does some stuff
mQuickAction.dismiss();
}
}); // end setOnClickListener
} // end onItemClick
} // end QAListener
} end BaseActivityForList
So, how do I arrange for the Checkbox to pick up the onClick, and the rest of the list item to pick up the onItemClick()?
After few hours of debugging and research. I got it working by
setting following properties on the checkbox:
android:focusable="false"
android:focusableInTouchMode="false"
Also ImageView may need to be configured as described in http://blog.sachin.name/?p=62 (Thanks to that person)
On my side, I discovered that item view MUST NOT be clickable and text views should not be clickable, either. It took me a while to realize that I called setClickable(true) on my item view somewhere in the code.
Just for complementing this accepted answer: in case of ImageButton instead of CheckBox it is not enough to set in xml focusable and focusableInTouchMode to false but during runtime focusable need to be set to false. That means to make sure onItemClick will be detected one must do smth like:
button.setFocusable(false);
so things work completely.. Original credits to author here . hope it will help to someone.
Cheers ;)
Edit: There is even more elegant solution try to add android:descendantFocusability="blocksDescendants" in root layout of list element. That will make clicks onListItem possible and separately u can handle Button or ImageButton clicks