Android WebView inside ListView onclick event issues - android

I have a ListView where each row has two webviews side by side, taking up the entire row. I've set up onListItemClick() in my ListActivity, but they are not fired when I tap on one of the rows (unless the place I happen to tap is outside the webview's border - but this isn't likely behavior, users would most likely want to tap on the image inside the webview).
I've tried setting setFocusable(false) and setFocusableInTouchMode(false), but those don't help.
Ideally this would operate exactly like Facebook's newsfeed, where you can tap on the profile picture of someone's wall post and it acts as if you've tapped the entire row (in FBs case the text for the wall post)

Figured it out, posting my solution in case someone else wants to do something similar:
I had to use an OnTouchListener, since OnClick and OnFocus weren't working. I extended a class that is reuseable:
private class WebViewClickListener implements View.OnTouchListener {
private int position;
private ViewGroup vg;
private WebView wv;
public WebViewClickListener(WebView wv, ViewGroup vg, int position) {
this.vg = vg;
this.position = position;
this.wv = wv;
}
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_CANCEL:
return true;
case MotionEvent.ACTION_UP:
sendClick();
return true;
}
return false;
}
public void sendClick() {
ListView lv = (ListView) vg;
lv.performItemClick(wv, position, 0);
}
}
The sendClick method could be overridden to do what's needed in your specific case. Use case:
WebView image = (WebView) findViewById(R.id.myImage);
image.setOnTouchListener(new WebViewClickListener(image, parent, position));

I managed to get this working with the following:
class NoClickWebView extends WebView {
public NoClickWebView(Context context) {
super(context);
setClickable(false);
setLongClickable(false);
setFocusable(false);
setFocusableInTouchMode(false);
}
}
You can use this class or just set these properties on a standard WebView.

Got it working in Android 4.4 using the same approach as bjdodson above but also overriding dispatchTouchEvent
public class NonClickableWebview extends WebView {
public NonClickableWebview(Context context, AttributeSet attrs) {
super(context, attrs);
setClickable(false);
setLongClickable(false);
setFocusable(false);
setFocusableInTouchMode(false);
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return false;
}
}

Setup to parent layout of each WebView:
android:descendantFocusability="blocksDescendants"
android:layout_width="match_parent"
android:layout_height="wrap_content"

I just tried what is suggested in this post WebView inside the Custom ListView: Click Listener is not Working, I dunno why but if you set these features in the XML they don't seem to work. Doing this dynamically does allow you to click on the listview item =)

I was using the following layout in ListView and it was working perfectly, ie its clicking, scrolling and so on.
TextView1
ImageView1 TextView2 ImageView2
TextView3
Then I have changed the layout with following, ie I have added with WebView control in left most corner in place of ImageView1
TextView1
WebView TextView2 ImageView2
TextView3
After doing this, clicking was not working for me.
I solve this problem by adding this to the Webview:
webView.setFocusable(false);
webView.setClickable(false);

Related

Android: How to listen to MouseEvent on other View, and handle the event in my custom view

For practicing purpose, I am create a customized Tooltip control. To use the Tooltip control, a hosting UIControl (e.g. a Button) will be assigned to my Tooltip control, and I want my Tooltip control is be able to listen to the mouse press event on the hosting control (i.e. the Button), and show / dismiss itself accordingly.
I am having problem finding a way to listening to mouse events of the hosting control. I tried:
Set the Hosting Control's setOnTouchListener, this works, but it will override the existing OnTouchListener of the Hosting Control, thus undeserable.
Go to the Hosting Control's ViewGroup, and add a **Observer to the ViewGroup. But there is no way to observe the mouse event on the ViewGroup.
So is listening to other control's mouse event doable from a custom view, if so, what's the recommended way to implement it ?
Thanks.
I also thought of another way to do it, as followed:
Get the ViewGroup of the hosting control;
In the ViewGroup, add a transparent view to listen to the mouse event.
In the handler of mouse event of the transparent view, check whether the mouse event is happened on the Hosting Control.
If happened on the Hosting Control, respond correspondingly.
I will try this approach after I post my question, but it seems to be resource-intensive way of implementing something seemingly straightforward.
I will let you know if this approach works or not, any comment / thought is very appreciated.
Thanks ~!
Try to use this approach. I've already tried this approach with OnClickListener and it works great.
public class CustomButton extends Button {
private OnTouchListener outsideListener;
private OnTouchListener innerListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (outsideListener != null) {
outsideListener.onTouch(v, event);
}
//some code here ...
}
};
#Override
public void setOnTouchListener(OnTouchListener listener) {
outsideListener = listener;
}
public CustomButton(Context context) {
super(context);
super.setOnTouchListener(innerListener);
}
public CustomButton(Context context, AttributeSet attrs) {
super(context, attrs);
super.setOnTouchListener(innerListener);
}
public CustomButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
super.setOnTouchListener(innerListener);
}
}
as I mentioned in my question, I thought of a way to implement what I wanted. I am sharing my way of implementing here. But still, it seems to be resource-intensive way of solving seemingly simple problem. If you have an easier solution, or any comment, please leave a comment. Much Appreciated ~!
My custom MaterialTooltip class:
public class MaterialToolTip {
/// Implementation
}
An example of how to use my MaterialToolTip class:
public class MainActivity extends AppCompatActivity {
MaterialToolTip toolTip;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// anchorButton is the button to which ToolTip will be added to.
Button anchorButton = (Button)findViewById(R.id.button);
this.toolTip = new MaterialToolTip.Builder(this)
//.anchorView property is used to specify the view that will use this tooltip
.anchorView(anchorButton)
.maxWidth(R.dimen.simpletooltip_max_width)
.build(); // that's all the consumer needs to do, as soon as the tooltip is attached to the View, the tooltip decided when to show / dismiss by listening to View's event.
}
}
Within my MaterialToolTip class:
Create a transparent view that listens to mouse event
#SuppressLint("ViewConstructor")
public class ToolTipPressInterceptView extends View {
OnAnchorViewMouseEventListener mListener;
ToolTipPressInterceptView(Context context, View anchorView) {
super(context);
//get the anchorView's onScreenLocation
int[] output = new int[2];
anchorView.getLocationOnScreen(output);
anchorViewRect = new RectF(output[0], output[1], output[0] + anchorView.getMeasuredWidth(), output[1] + anchorView.getMeasuredHeight()) ;
//set the dimension to match parent
this.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
this.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:{
if (anchorViewRect.contains(event.getX(), event.getY())){
mListener.onMouseEvent(AnchorViewMouseEventType.PRESSED);
}
}
case MotionEvent.ACTION_UP:{
if (anchorViewRect.contains(event.getX(), event.getY())){
mListener.onMouseEvent(AnchorViewMouseEventType.RELEASED);
}
}
}
return false;
}
});
}
}
Get the ViewGroup of the hosting control, and add the transparent view to the ViewGroup;
private MaterialToolTip(Builder builder){
//.. Other initialisation logic
//.. add the TransparentView to the ViewGroup;
mRootView = (ViewGroup)mAnchorView.getRootView();
ToolTipPressInterceptView view = new ToolTipPressInterceptView(mContext, mAnchorView);
//mAnchorViewTouchListener is listener to Mouse Event
view.setOnTouchListener(mAnchorViewTouchListener);
mRootView.addView(view);
}
If happened on the Hosting Control, respond correspondingly.
private final View.OnTouchListener mAnchorViewTouchListener = new
View.OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event){
int action = MotionEventCompat.getActionMasked(event);
switch(action) {
case (MotionEvent.ACTION_DOWN) :
// show
show();
case (MotionEvent.ACTION_UP) :
// dismiss
dismiss();
}
return false; //return false so the other handlers is able to
}
};

Cannot select right button on ListView with FastScroll enabled

I have a ListView with image on left, title & subtitle in the centre and an ImageButton on the right (this button doesn't have any margin on the right).
<ListView
android:id="#+id/contacts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="#android:color/transparent"
android:fastScrollEnabled="true"
android:scrollbarStyle="outsideInset"/>
I have enabled fast scrolling for this ListView. When I try to click the right ImageButton the scrollbar comes in focus and ListView starts scrolling. I am not able to select the button on the right. Please help me out.
You need to override ListView class and its onInterceptTouchEvent method.
public class CustomListView extends ListView {
public CustomListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
setFastScrollEnabled(false);
boolean possibleResult = super.onInterceptTouchEvent(ev);
setFastScrollEnabled(true);
boolean actualResult = super.onInterceptTouchEvent(ev);
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
return possibleResult && actualResult;
}
return super.onInterceptTouchEvent(ev);
}
}
And it will look like:
However, the issue which you observe is an expected behaviour.
The proper fix would be to add padding to the end of your row.
Take a look on the Google's PhoneBook app, for example:
As you can see here, the cell-size is smaller, than 100% of screen's width.
I hope, it helps

How to handle click-event in RecyclerView.ItemDecoration?

I have a RecyclerView (with LinearLayoutManager) and a custom RecyclerView.ItemDecoration for it.
Let's say, I want to have buttons in the decoration view (for some reason..).
I inflate the layout with button, it draws properly. But I can't make the button clickable. If I press on it, nothing happening(it stays the same, no pressing effect) and onClick event is not firing.
The structure of ItemDecoration layout is
<LinearLayout>
<TextView/>
<Button/>
</LinearLayout>
And I'm trying to set listener in ViewHolder of the decoration
class ItemDecorationHolder extends RecyclerView.ViewHolder {
public TextView header;
public Button button;
public HeaderHolder(View itemView) {
super(itemView);
header = (TextView)itemView.findViewById(R.id.header);
button = (Button)itemView.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//.. Show toast, etc.
}
});
}
}
And i'm drawing the decoration in onDrawOver method. (actually, I'm modifying this codebase: https://github.com/edubarr/header-decor )
Any ideas? Is it doable?
Thanks!
While the real header is scroll off the screen, the visible one is drawing on canvas directly ,not like a normal interactive widget.
You have these options
Override RecyclerView.onInterceptTouchEvent(), though with some invasiveness so I prefer the next one.
Make use of RecyclerView.addOnItemTouchListener(), remember the motion event argument has been translated into RecyclerView's coordinate system.
Use a real header view, but that will go a little far I think.
If you take option 1/2, Button.setPressed(true) and redraw the header will have a visual press effect.
In addition to what Neil said,
the answer here might help.
Passing MotionEvents from RecyclerView.OnItemTouchListener to GestureDetectorCompat
And then you just need to calculate the height of the header and see if the click falls onto that header view and handle the event yourself.
private class RecyclerViewOnGestureListener extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
float touchY = e.getY();
ALLog.i(this, "Recyclerview single tap confirmed y: " + touchY);
//mGroupHeaderHeight is the height of the header which is used to determine whether the click is inside of the view, hopefully it's a fixed size it would make things easier here
if(touchY < mGroupHeaderHeight) {
int itemAdapterPosition = mRecyclerView.getLayoutManager().getPosition(mRecyclerView.findChildViewUnder(0, mGroupHeaderHeight));
//Do stuff here no you have the position of the item that's been clicked
return true;
}
return super.onSingleTapConfirmed(e);
}
#Override
public boolean onDown(MotionEvent e) {
float touchY = e.getY();
if(touchY < mGroupHeaderHeight) {
return true;
}
return super.onDown(e);
}
#Override
public boolean onSingleTapUp(MotionEvent e) {
float touchY = e.getY();
if(touchY < mGroupHeaderHeight) {
return true;
}
return super.onSingleTapUp(e);
}
}
As Neil is pointing out, things are getting more complicated than that. However by definition you can't.
So, why not including good libraries that do that and more?
I propose my hard work for clickable sticky header in my FlexibleAdapter project, which uses a real view (not decorators) to handle click events on headers when sticky.
There's also a working demo and a Wiki page on that part (and not only).

How to Get the index of particular(custom) view from ViewGroup (FrameLayout) after doing onLongClick on the View

Can someone help me with my Syntax? I am dynamically adding some views(custom view) to a FrameLayout that is already defined in XML. those custom view's are different type or same type. I'm able to add views to the screen but unable delete a particular view (either it is same type or different) from ViewGroup. those custom view having onTouch().
Here i'm facing problem : unable to trigger for long click, always takes touch listener
I have to create two options here if user selects a view when long click on that
change background color
delete view.
EDIT : according to tao suggestion i am able to get index of long pressed view if there is no touch listener's to view. but i have to implement touch listeners along with long press...
How can I do this?
if each one of your views have a delete button or something like that, you can do that:
yourLayout.setTag(view);
on your delete part:
yourLayout.setOnLongClickListener(new Button.OnLongClickListener() {
public boolean onLongClick(View view) {
...
...
yourLayout.removeView((View) view.getTag());
return _value;
}
i hope it helps you.
Ok, your problem is "unable to trigger for long click, always takes touch listener", but this is not enought. I need more details:
which view you supposed to handle the long click, parent view or children view?
which listener you used to handle long click, android.view.View.setOnLongClickListener or android.view.GestureDetector?
actually I done the same job last week. My experiences is: do not using android.view.View.setOnLongClickListener neither android.view.GestureDetector, handle the long click on parent view by yourself. View.java is a good example.
EDIT:
I dont's have compiler on my hand, so I just typing the pseudo-code which will handle the long press by self, for the real-code, the View.java will give you the best answer.
Firstly, you need a runnable to implement your action
class CheckForLongPress implements Runnable {
public void run() {
if (getParent() != null) {
// show toast
}
}
}
Secondly, modify your onTouchEvent to detecting long press
boolean onTouchEvent(...) {
switch (event.getAction()) {
case MotionEvent.ACITON_DOWN:
// post a delayed runnable to detecting long press action.
// here mPendingCheckForLongPress is instance of CheckForLongPress
postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());
...
break;
case MotionEvent.ACTION_MOVE:
// cancel long press action
if (distance(event, lastMotionEvent) > mTouchSlop) {
removeCallbacks(mPendingCheckForLongPress);
}
...
break;
case MotionEvent.ACTION_UP:
// cancel long press action
removeCallbacks(mPendingCheckForLongPress);
...
break;
EDIT again:
following is real code, not pseudo one, which is very simple and shows how to handle long press in View.onTouchEvent(), may it would be help.
public class ItemView extends View {
public ItemView(Context context) {
super(context);
}
public ItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ItemView(Context context, AttributeSet attrs) {
super(context, attrs);
}
Runnable mLongPressDetector = new Runnable() {
public void run() {
Toast.makeText(getContext(), "Hello long press", Toast.LENGTH_SHORT).show();
}
};
MotionEvent mLastEvent;
#Override
public boolean onTouchEvent(MotionEvent event) {
mLastEvent = MotionEvent.obtain(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
postDelayed(mLongPressDetector, ViewConfiguration.getLongPressTimeout());
break;
case MotionEvent.ACTION_MOVE:
final int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
if (Math.abs(mLastEvent.getX() - event.getX()) > slop ||
Math.abs(mLastEvent.getY() - event.getY()) > slop) {
removeCallbacks(mLongPressDetector);
}
break;
case MotionEvent.ACTION_UP:
removeCallbacks(mLongPressDetector);
break;
}
return true;
}
}
Look here:
http://developer.android.com/reference/android/view/View.html
there is a method that int getId(). so every view has an unique idetifier. you can use it for ever view.
Here my question is how can i identify the selected view from the
group of views when user touch on that view.
I assume TwoPointsDraw & OnePointDraw is extending View.
So in this case what you can do is after creating object of TwoPointsDraw.assign unique Id or Tag to it.
TwoPointsDraw drawView = new TwoPointsDraw(context);
drawView.setTag("unique identifier"); <-Must be object type
drawView.setId(unique id); <-must be integer type
And when you will click particular view.You can check its identity using.
view.getTag() or view.getId()
Snippet should look like
circle.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
Log.i("Long", v.getTag().toString());
return false;
}
});
Hope this helps you.
For your OnLongClickListener to work, you should return false when you detect a long click in your onTouch function!
For example:
In your GestureDetector, to give a chance for your onLongClickListener you should:
#Override
public boolean onDown(MotionEvent ev) {
return false;
// return super.onTouchEvent(ev);
}
Otherwise:
In your Views, implement the setOnLongClickListener function and save the onLongClickListener passed instance. Whenever a long click is detected, call the instance's onLongClick function.

Android - how to disable a ScrollView and all children

edited for clarity
I feel like this question already has an answer, but I can't find one.
I have a ScrollView in my layout, and it contains a variety of clickable views.
Under a specific condition I would like to disable clicks and events for the ScrollView and ALL of its children.
The following have not been helpful:
ScrollView.setEnabled(false)
ScrollView.setClickable(false)
ScrollView.setOnTouchListener(null)
As well as:
(parent view of the ScrollView).requestDisallowInterceptTouchEvent()
I have created a custom ScrollView with the following code:
public class StoppableScrollView extends ScrollView
{
private static boolean stopped = false;
public StoppableScrollView(Context context)
{
super(context);
}
public StoppableScrollView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
if(stopped)
{
return true;
}
else
{
super.onInterceptTouchEvent(ev);
return false;
}
}
#Override
public boolean onTouchEvent(MotionEvent ev)
{
if(stopped)
{
return true;
}
else
{
super.onTouchEvent(ev);
return false;
}
}
public static void setStopped(boolean inBool)
{
stopped = inBool;
}
public static boolean getStopped()
{
return stopped;
}
}
Using only onTouchEvent() will stop the scrolling, but not the clicking of child views.
Using only onInterceptTouchEvent() makes it such that when clicks work scrolling does not, and vice versa.
Using both onTouchEvent() and onInterceptTouchEvent() successfully stops unwanted clicks on child views when stopped is 'true' but it also disables scrolling regardless of the state of stopped.
Is there an easier way to get this behaviour, or is there a way to modify the StoppableScrollView class so that it will handle these touch events properly?
What probably should help is the following (because I had similar problems):
In the ScrollView you should do a RelativeLayout as Main Child (ScrollView does accept only 1 main child anyway). This RelativeLayout should of course of fill_parent in both directions.
At the really end of the RelativeLayout (after all other children), you could put now a LinearLayout with transparent background (#00FFFFFF) which has also fill_parent in both directions. This LinearLayout should have Visibility = View.GONE (by default)
Also you have to attach an empty OnClickListener to it. Now, because of zOrder if you make this LinearLayout Visibility = View.Visible it will catch all the events and avoid clicking the children above!
As scrollview allows immeditate one child say in my case i have linear layout.and in this linear layout i have other conreolls.
now our first task is to get this linear layout so what we can write is
LinearLayout l = (LinearLayout) scrollview.getChildAt(0);
now after getting this linear layour we can easily access other controlls placed inside it via this code and disable it.
for(int i =0; i<l.getChildCount(); i++)
{
Log.i(TAG,"child "+ l.getChildAt(i));
l.getChildAt(i).setEnabled(false);
}

Categories

Resources