Using ViewStub within ListViews causes onItemClick problems - android

I have an interesting dilemma. I have a ListView which I am using to contain CardViews. However, each CardView is actually a pair of two: one default (front) card, and then a back card that I am animating as a flip upon the user clicking the card. Since the user may not click on any list item, I am using a ViewStub as the back card, and will lazy inflate upon the list item click.
So the first time the user clicks a card to flip to the back, everything looks good and the flip works (onItemClick() gets called from clicking anywhere within the card). However, any further attempts to flip over to the front again do not trigger the onItemClick() call, UNLESS I click the very top of the view (list item).
I've done lots of debugging (changing focusability and sizing) and figured out that this is being caused only after the ViewStub inflation. Making both front and back cards normal CardViews allows the onItemClick() to fire successfully every time.
So what is the reason why the ViewStub is fubaring my onItemClick()? Is there a way to fix it, rather than have all of my views inflated automatically?
Here is the code I'm using:
card_container_layout.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5dp">
<include layout="#layout/card_front_view"
android:id="#+id/main_card_view_layout" />
<ViewStub android:id="#+id/stub_flipped_card"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inflatedId="#+id/flipped_card"
android:layout="#layout/card_back_view" />
</FrameLayout>
card_front_view.xml
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_height="wrap_content"
card_view:cardCornerRadius="5dp"
android:id="#+id/card_view"
android:descendantFocusability="blocksDescendants" >
<LinearLayout
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="#dimen/card_view_height">
<ImageView android:id="#+id/img_main_card"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/my_image"
android:background="#ff10fffd" />
<TextView
android:id="#+id/lbl_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Name"
style="#style/main_card_title_text"
android:layout_alignParentBottom="true"
android:padding="15dp"
android:background="#5b000000"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_gravity="center_horizontal"
android:padding="4dp" >
<TextView
android:id="#+id/lbl_item_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
style="#style/main_card_text" />
<ImageView android:id="#+id/img_sync_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:layout_centerVertical="true"/>
</RelativeLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
card_back_view.xml
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_height="wrap_content"
android:layout_width="match_parent"
card_view:cardCornerRadius="5dp"
android:id="#+id/flipped_card"
android:alpha="0"
android:descendantFocusability="blocksDescendants">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Here are the items:"
android:gravity="center"
style="#style/card_flipped_title_text"
android:background="#ad000000"/>
<GridView android:id="#+id/grid_images"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:numColumns="3">
<!-- make believe there were items here -->
</GridView>
</LinearLayout>
</android.support.v7.widget.CardView>

Ok, I did more digging into it and it appears that it's not the ViewStub at all causing it, but in fact the GridView. For reasons that I was not able to figure out, the GridView continued to hijack all touch events, despite setting focusable and clickable to false and blocking descendent focusability as seen in the sample XML above.
Therefore the hacky way I've worked around it, is to intercept all touch events from the GridView, and within the onTouchListener(), if I detect a MotionEvent.ACTION_UP, indicating that I have been touching the screen and just released, I will then manually perform the same action as I am doing in the rest of the CardView.
itemGrid.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View grid, MotionEvent event) {
// hacky way to make sure that we absorb the GridView's focus stealing and still do the right thing
if (MotionEvent.ACTION_UP == event.getAction()) doExpectedStuff();
return true; // absorb the touch here
}
});
Sorry that this isn't the most elegant of solutions, and agree that capturing an up event is not a one-size-fits-all approach for most people, but it helped in my specific situation. Hope that this still may help others.

Related

Limit focus to only one fragment in a container with multiple added fragments

I have multiple fragments which use the same container, and when a fragment is added over another, I want focus to be shifted only among the views in recently added fragment. I tried to solve it using onBackstackChangeListener() but it doesn't seem to work. As in the image I want focus only on the Pin Entry fragment but the focus is shifting to the "Enter" buttons on the fragment behind the Pin Entry Fragment as well.Please help.
I found a work around to this problem but it's not a real solution, Best answer will be marked as correct answer.
Work Around:
Add Views around your layout that needs the focus to stay with in itself. For Example:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:orientation="horizontal"/>
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:focusable="true"
android:id="#+id/left_view"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="#+id/buyOrWatch"
android:layout_width="90dp"
android:layout_height="30dp"
android:layout_margin="5dp"
android:background="#drawable/movie_buy_selector"
android:focusable="true"
android:gravity="center"
android:text="#string/buy"
android:textColor="#color/color_white" />
<TextView
android:id="#+id/preview"
android:layout_width="90dp"
android:layout_height="30dp"
android:layout_margin="5dp"
android:layout_marginTop="15dp"
android:background="#drawable/movie_buy_selector"
android:focusable="true"
android:gravity="center"
android:text="#string/preview"
android:textColor="#color/color_white" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:focusable="true"
android:id="#+id/right_view"/>
I placed the layout with the textviews in between the two focusable views with 0dp width.
Now, in your fragment class, bind those Views ,then define a focusChangeListener() for those views. for example if leftView binds view with id android:id="#+id/left_view" and buyOrWatch with TextView having android:id="#+id/buyOrWatch",then,
leftView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View view, boolean hasFocus) {
if(hasFocus){
buyOrWatch.requestFocus();
}
}
});
You can define similar focus change listeners for each views like above, then as long as the fragment is visible, focus stays with in the fragment. Hope this helps someone.
ViewGroup viewGroup;//assign your viewGroup
viewGroup.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
This must Work
Reference from Can someone explain descendantFocusability = afterDescendants?
If you are using single container for all fragment then you need to set topmost root of your fragments clickable. Otherwise fragment behind can grab the focus for clickable items. See the fragment layout below for example.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:background="?selectableItemBackground"
android:gravity="center_vertical"
android:textColor="#color/white"
android:textSize="#dimen/normal_text" />
</LinearLayout>

How to populate a recyclerview on click of a button in a cardview

I am struck into a situation where I am finding no way to proceed. Actually I have a RecyclerView of cardViews and there is an adapter that populates the card with data. I have to show some restaurants in the card. On click of a button in the Cardview, I have to show another recyclerview that will be populated with cardViews containing the branches of that restaurant.
Here i have just one Model that can return both type of data. I am not getting whether I should use 1 Adapter with multiple viewHolders or 2 Adapters. My View is also a single layout.
<?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:layout_marginBottom="#dimen/margin_medium">
<android.support.v7.widget.CardView
android:id="#+id/recipient_card"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
android:elevation="20dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="#dimen/receipient_card_height"
android:orientation="horizontal"
android:id="#+id/linear_layout">
<ImageView
android:id ="#+id/arrow_button"
android:layout_width="#dimen/size_info_icon"
android:layout_height="#dimen/size_info_icon"
android:layout_marginLeft="#dimen/activity_horizontal_margin"
android:layout_gravity="center" />
<com.inquirly.opinify.View.CustomTextView
android:id="#+id/business_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="#style/label_small"
android:layout_marginLeft="#dimen/activity_horizontal_margin"
android:layout_gravity="center"
android:layout_weight="10"
android:text="JustBake"/>
<ImageView
android:id="#+id/recipient_business_logo"
android:layout_marginLeft="#dimen/margin_small"
android:layout_gravity="center"
android:layout_width="#dimen/receipient_business_logo_size"
android:layout_height="#dimen/receipient_business_logo_size"/>
<ImageView
android:id="#+id/expand_button"
android:layout_marginLeft="#dimen/margin_small"
android:layout_gravity="center"
android:padding="#dimen/margin_medium"
android:layout_width="#dimen/height_button"
android:layout_height="#dimen/height_button"
/>
</LinearLayout>
</RelativeLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_recipient_branch_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visible= "false"
android:padding="#dimen/margin_medium"/>
</RelativeLayout>
I have made the inner recyclerView invisible in the beginning. When the user clicks on the arrow button in the main recycler's CardView button, the second recylerview should appear with the list of branches.
This is the screen shot of what i am trying to achieve.
Why don't you do a implement a expandable RecyclerView instead. Check out this article. Also it's recommended that you check this question out.

RecyclerView with nested RecyclerView - make nested RecyclerView clickable as a whole view

I use a RecyclerView that shows a list of entries. Each entry hosts another RecyclerView that is a list of images.
I now want to make this nested RecyclerView clickable, not the items of it, but the whole view.
How can I achieve that?
Problem:
setting the onClickListener for the main view of the nested RecyclerView does work, but only if I click outside the RecyclerView itself
clicking on the nested RecyclerView does not hand on the clicks to the parent view (I even tried to set clickable, focusable, focusableIntouch to false, still, the touch is not delegated but consumed by the nested RecyclerView...
Here's the view for the wrapping adapter:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardCornerRadius="0dp"
android:layout_margin="5dp"
android:foreground="?android:attr/selectableItemBackground" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:id="#+id/rlTop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:text="Datum"
android:textStyle="bold"
android:id="#+id/tvDate" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="Info"
android:id="#+id/tvInfo" />
</RelativeLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/rvData"
android:clickable="false"
android:focusableInTouchMode="false"
android:focusable="false"
android:layout_below="#+id/rlTop"
android:layout_width="match_parent"
android:layout_height="68dp"
android:scrollbars="horizontal" />
</RelativeLayout>
</android.support.v7.widget.CardView>
I looked into RecyclerView source and this helped:
recyclerView.setLayoutFrozen(true)
This is an old question, but i have the same task and didn't find good solution.
I just placed the transparent view over recyclerview and set click listener to this view.
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:cardBackgroundColor="#color/white"
app:cardCornerRadius="0dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="#+id/dataLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="#dimen/history_margin_v"
android:paddingEnd="#dimen/history_margin_h"
android:paddingLeft="#dimen/history_margin_h"
android:paddingRight="#dimen/history_margin_h"
android:paddingStart="#dimen/history_margin_h"
android:paddingTop="#dimen/history_margin_v">
<TextView
android:id="#+id/day"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="#dimen/widget_margin_v"
android:textColor="#color/font_blue"
android:textSize="#dimen/font_size_large"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/images"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<View
android:id="#+id/clickView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:layout_alignLeft="#+id/dataLayout"
android:layout_alignRight="#+id/dataLayout"
android:layout_alignStart="#+id/dataLayout"
android:layout_alignEnd="#+id/dataLayout"
android:layout_alignTop="#+id/dataLayout"
android:layout_alignBottom="#+id/dataLayout"/>
</RelativeLayout>
</android.support.v7.widget.CardView>
In order to do the entire row clickable and not every image u need to implement a custom onItemClickListener (name comes from listview , sorry).
Take a look at this link
this worked out perfectly for me.
Edit:
The nested recycler steals your clicks. in order to fix that u will need to create a cusom touch listener and pass it to the nested recycler. Just like the one that u put to the outer ercycler but pass the event to the outer.
Edit 2:
Add a selector with two states just like a button to the entire row, then at the onclick on the inner recycler setSelected(true) the row and postDelay with 50ms for setSelected(false) this will give the "click" effect.

WebView is blocking my ExpandableListView's expansion

I've been working with ExpandableListViews before and I know about checkBoxes, Buttons, etc.
It is essential to setFocusable to false for any of these widgets that could be found on the XML that will serve as a groupView of our list, othervise, clicking on the group cell will not expand the group, since these widgets will steal focus from it.
BUT, If I put WebView in my GroupView, and setFocusable & setFocusableInTouchMode to false, I will still NOT be able to expand the group.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="91dp"
android:background="#ffffff">
<ImageView
android:layout_width="30dp"
android:layout_height="91dp"
android:id="#+id/sth_cell_img_correct"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:src="#drawable/icon_checked" />
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="20dp"
android:focusable="false"
android:focusableInTouchMode="false"
android:id="#+id/webView2" />
</LinearLayout>
But, if I remove WebView from the XML, I can expand / shrink cells with no trouble whatsoever.
There must be some other thing I should do here. Some other attribute should be set to false, or something like that, but I cannot figure out what exactly.
Does anyone has a clue what to do in order to have WebView in group XML and be able to expand the group itself ?
Try setting this on the root element
android:descendantFocusability="blocksDescendants"
And then make the WebView unclickable with
android:clickable="false"
The final XML should be something like this:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="91dp"
android:descendantFocusability="blocksDescendants"
android:background="#ffffff">
<ImageView
android:layout_width="30dp"
android:layout_height="91dp"
android:id="#+id/sth_cell_img_correct"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:src="#drawable/icon_checked" />
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="false"
android:id="#+id/sth_web_view" />
</RelativeLayout>
Hope this helps.

Making list item unselectable

I have a list view with my custom content. I have on message iamgeview, one call and other option. So that user can select any one of the option, but if user select any text(touch the textview) or any other portion of ListView, it should not respond. I have small UI.
So if user touches on MyText, nothing should be done. Not even Visual feed back what we get when we touch list tiem normally in android. But touch event for call, or message should come.
My code snippet
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/listitem_contact"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:id="#+id/tv_contact"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:clickable="false"
android:gravity="left|center_vertical"/>
<ImageView
android:id="#+id/iv_call"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#drawable/list_item_image_color"
android:src="#drawable/ic_call"/>
<ImageView
android:id="#+id/iv_message"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#drawable/list_item_image_color"
android:src="#drawable/ic_textmessage"/>
<ImageView
android:id="#+id/ic_phone"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#drawable/list_item_image_color"
android:src="#drawable/ic_phone"/>
</LinearLayout>
PS: On touching the portion of textview, by default first ImageView is getting selected. I don't want that any wodget should respond unless touched
The visual feedback on touching an item is a style on the ListView. You can change it as below:
<ListView
android:listSelector="#android:color/transparent"
android:cacheColorHint="#android:color/transparent"
/>
However, it's best to define your own styles and apply them to the views.

Categories

Resources