I am facing multitouch issue. The problem is I can simultaneously touch two buttons on my screen. I know this question is asked several times in this forum and the only solution is to declare android:splitMotionEvents="false" in your parent layout. But after declaring this the issue remains. Is it the problem with the hardware or is it with the code ? Any pointer here is appreciated.
This issue appears beacuse since android 4.0 each onClick performed in a new thread.
How i solved it:
//1. create your own click listener
public abstract class AbstractCarOnClickListener {
protected static volatile boolean processing = false;
protected void executeBlock() {
ActivityUtil.scheduleOnMainThread(new Runnable() {
public void run() {
processing=false;
}
}, 400);
}
}
//2. create subclass of your listener
public abstract class AppButtonsOnClickListener extends AbstractCarOnClickListener implements View.OnClickListener {
public void onClick(View v) {
if(processing) return;
try{
processing=true;
onCarButtonClick(v);
} finally {
executeBlock();
}
}
public abstract void onCarButtonClick(View v);
}
//3. set listener to your view
public void onClick(View v) {
clickListener.onClick(v);
}
public OnClickListener clickListener = new AppButtonsOnClickListener(){
public void onCarButtonClick(View v) {
hintContainer.setVisibility(View.GONE);
if (v == cancelButton) {
listener.onCancelButtonClicked();
}
}
}
This is what worked for me. In addition to setting the android:splitMotionEvents="false" on every ViewGroup that contains the buttons I put this in MyAdapter.getView()...
view.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View cell, MotionEvent event) {
// Process touches only if: 1) We havent touched a cell, or 2) This event is on the touched cell
if (mTouchedCell == null || cell.equals(mTouchedCell)) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
cell.startAnimation(mShrink);
mTouchedCell = cell;
} else if (action == MotionEvent.ACTION_CANCEL) {
if (cell.equals(mTouchedCell)) {
cell.startAnimation(mGrow);
}
mTouchedCell = null;
return true;
} else if (action == MotionEvent.ACTION_UP) {
cell.startAnimation(mFadeOut);
mTouchedCell = null;
}
return false;
}
return true;
}
});
...and of course this in the adapter...
private static View mTouchedCell = null;
Related
i'm using backbutton as interface from activity but it's not working properly for me because on backpress showing 0 size of arraylist
// here is the activity class from where i'm getting backbutton interface..
public class Multiple_Images extends AppCompatActivity {
#Override
public void onBackPressed() {
if(twice ==true){
Intent intent =new Intent(this,MainActivity.class);
startActivity(intent);
}ImageAdapter imageAdapter =new ImageAdapter(this);
imageAdapter.onBackPress();
Toast.makeText(this, "Press twice", Toast.LENGTH_SHORT).show();
twice =true;
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
twice =false; } }, 2000); }}
//here is the adapter class here i'm using backbutton
public class ImageAdapter extends BaseAdapter implements onBackPressListener {
ArrayList<String> selectedArraylist ;
#Override
public boolean onBackPress() {
selectedArraylist.clear();
Toast.makeText(context, "All values unselected", Toast.LENGTH_SHORT).show();
return true;
}
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
urimodel=new ArrayList<>();
final ImageView imageGrid ;
Activity activity = (Activity) context;
actionMode = activity.startActionMode(new Actionmode());
final GridModel gridModel=(GridModel) this.getItem(i);
if(view==null) {
view = LayoutInflater.from(context).inflate(R.layout.model, null);
selectedArraylist =new ArrayList<>();
}
final CardView cardView= (CardView)view.findViewById(R.id.cardview_image);
imageGrid = (ImageView) view.findViewById(R.id.grid_image);
// gridText = (TextView) view.findViewById(R.id.grid_text);
imageGrid.setScaleType(ImageView.ScaleType.CENTER_CROP);
// imageGrid.setScaleType(ImageView.ScaleType.CENTER_CROP);
Picasso.get().load(gridModel.getImage()).resize(200,200).into(imageGrid);
if (selectedArraylist.contains(gridModel.getImage_text())) {
cardView.setCardBackgroundColor(CARD_SELECTED_COLOR);
}else {
cardView.setCardBackgroundColor(Color.WHITE);
}
return view;
}
}
Simply you can do this inside onBackPressed
#Override
public void onBackPressed() {
if (twice == true) {
super.onBackPressed(); //this backs to the previous activity, if you want to stay with Intent, add finish() after startActivity()
return;
} else {
for (int i = 0; i < list.size(); i++) {
if (gridView.isItemChecked(i)) {
gridView.setItemChecked(i, false);
}
}
//selectedArraylist.clear(); this is clearing your array of selected items
}
twice = true;
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
twice = false;
}
}, 2000);
}
I don't know, why did you put selectedArraylist =new ArrayList<>(); in adapter getView() method. getView() is fired every time, when a new list item is inflated, that mean every time, when you are changing adapters source, scrolling list this method is called, and every time you are initialize you array, and all data inside lost. You should treat an adapter class just like a tool for displaying items, and all actions like above make outside adapter.
pretty much easy,
I give you my own project code, hope it help you.
StudentFragment.java:
private void MultiSelected_Student(int position) {
Student data = adapter_class.getItem(position);
if (data != null) {
if (selectedIds.contains(data)) selectedIds.remove(data);
else selectedIds.add(data);
}
}
private void Remove_MultiSelected() {
try {
selectedIds.clear();
} catch (Exception e) {
e.printStackTrace();
}
}
public void Group_UnSelect() {
Remove_MultiSelected();
MultiSelected = false;
fab.setVisibility(View.GONE);
homeeActivity.studentsMultiSelect = false;
notifyy();
}
private void notifyy() {
adapter_class.notifyDataSetChanged();
}
HomeActivity.java:
public boolean studentsMultiSelect = false;
#Override
public void onBackPressed() {
if (studentsMultiSelect) {
studentFragment.Group_UnSelect();
} else {
super.onBackPressed();
}
}
so i had a AsyncTask with RunOnUiThread to interactively change ui elements and add TableRows the problem is on the listeners in TableRows. only one works but the others don't. this is a simple from my code
public class SearchAsync extends AsyncTask<String, String, String> {
private Context mContext;
private Activity mAct;
#Override
protected void onPreExecute() {
super.onPreExecute();
}
public void mytask(Context context, Activity act) {
mContext = context;
mAct = act;
}
#Override
protected String doInBackground(final String... arg0) {
String sc2 = "Alpha";
mAct.runOnUiThread(new Runnable() {
#Override
public void run() {
GridLayout gey = (GridLayout) mAct.findViewById(R.id.greylayout);
TableLayout tbly = (TableLayout) mAct.findViewById(R.id.listviewtable);{
final TableRow tbro = new TableRow(mContext);
TextView tev = new TextView(mContext);
tbro.setBackground(mAct.getResources().getDrawable(R.drawable.mybgbg));
tev.setTextColor(Color.WHITE);
tev.setText(sc2);
tbro.addView(tev);
tbro.setOnClickListener(new View.OnClickListener() { // this listener doesn't work.
public void onClick(View v) {
System.out.println("yey! it works");
}
});
tbro.setOnLongClickListener(new View.OnLongClickListener(){ // this listener doesn't work too.
#Override
public boolean onLongClick(View v){
System.out.println("yey! it works too!");
return true;
}
});
tbro.setOnTouchListener(new View.OnTouchListener() { // this listener works perfectly.
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
tbro.setBackground(mAct.getResources().getDrawable(R.drawable.mybgi));
} else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
tbro.setBackground(mAct.getResources().getDrawable(R.drawable.mybgbg));
}
return true;
}
});
tbro.setId(R.id.layoutx1);
tbro.setLongClickable(true);
tbro.setClickable(true);
tbly.addView(tbro);
}
}
}
TableRow tete = (TableRow)mAct.findViewById(R.id.layoutx1);
});
}
catch(JSONException e){
Log.e(TAG, "Json parsing error: " + e.getMessage());
);
}
}
return null;
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(null);
final String rslt;
rslt = result;
mAct.runOnUiThread(new Runnable() {
#Override
public void run() {
Toast toast = Toast.makeText(mContext, rslt, Toast.LENGTH_SHORT);
toast.show();
}
});
}
So only OnTouchListener works but the others don't.. and i don't know why.
this is only a problem in runOnUiThread and runonmainthread.
i tried the code onPreExecute() method and also onPostExecute. nothing works.
but in the MainActivity it works just fine.
Thanks for reading my question.
This is happening because you are returning true from onClick(), which indicates to the system that you have "consumed" the event.
View.OnTouchListener | Android Developers
The touch event would happen before the click event, because an ACTION_DOWN would trigger a touch event, but you need to have an ACTION_UP also before you can have a click event.
When you return true from onTouch(), this consumes the touch event, preventing the ACTION_UP from triggering the click event.
As a general guideline, you shouldn't use OnTouchListener and OnClickListener together.
It looks like you are using the OnTouchListener only to change the background. A StateListDrawable would be much better for that. You set the background once, and the state list takes care of the rest:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="#drawable/mybgi"
android:state_pressed="true"/>
<item
android:drawable="#drawable/mybgbg"/>
</selector>
State List | Drawable Resources | Android Developers
I want to change the images in image view on Onclick function. I have already tried this:
bt_audiocapture.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if (checkPermission()) {
if (bt_audiocapture.getResources().getDrawable(R.drawable.ic_mic).equals(R.drawable.ic_mic)) {
start();
bt_audiocapture.setImageResource(R.drawable.ic_stop);
} else if (bt_audiocapture.getResources().getDrawable(R.drawable.ic_stop).equals(R.drawable.ic_stop)) {
stop();
bt_audiocapture.setImageResource(R.drawable.ic_play);
} else if (bt_audiocapture.getResources().getDrawable(R.drawable.ic_play).equals(R.drawable.ic_play)) {
play();
}
}
}
});
bt_audiocapture.getResources().getDrawable(R.drawable.ic_mic) returns a Drawable object. You can't compare it to R.drawable.ic_mic, which is integer. That is something like comparing a car to green color.
To accomplish your task, make some field like private int state = 0;, and some constants like
private final STATE_PLAYING = 1;
private final STATE_STOPPED = 2;
private final STATE_NONE = 0;
and then:
if (state == STATE_NONE) {
start();
bt_audiocapture.setImageResource(R.drawable.ic_stop);
state = STATE_PLAYING;
} else if (state == STATE_PLAYING) {
stop();
bt_audiocapture.setImageResource(R.drawable.ic_play);
state = STATE_STOPPED;
} else if (state == STOPPED) {
play();
state = STATE_PLAYING;
}
Your equals expressions always return false, I would expect that none of your if blocks is executed. Did you debug that code?
I would suggest to keep the state in another variable, e.g. with an enum.
Additionally you should use setImageDrawable for performance reasons. See the javadoc of setImageResource:
This does Bitmap reading and decoding on the UI thread, which can cause a latency hiccup
Try this code, its working for me:-
img = (ImageView) findViewById(R.id.img);
img.setTag(0);
img.setImageResource(R.drawable.images);
img.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (Integer.parseInt(img.getTag().toString()) == 0) {
img.setImageResource(R.drawable.cam);
img.setTag(1);
} else {
img.setImageResource(R.drawable.images);
img.setTag(0);
}
}
});
I want to implement custom onBackPressed methods for all my fragments which are included in my Main activity. But I am not getting a hook when my device back is pressed. I tried to implement few stuffs available in SOF, but none of them work properly.
Please Help!
I tried this in onCreateView:
rootView.setFocusableInTouchMode(true);
rootView.requestFocus();
rootView.setOnKeyListener( new OnKeyListener()
{
#Override
public boolean onKey( View v, int keyCode, KeyEvent event )
{
if( keyCode == KeyEvent.KEYCODE_BACK)
{
return true;
}
return false;
}
} );
Use below code hopefully this code will help you.
rootView.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
backFlag = backFlag++;
if (backFlag == 2) {
getActivity().finish();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
backFlag = 0;
return true;
}
return false;
}
});
Same issue here. I solve this by setting actionbar title to fragment and then check onBackPressed which fragment is currently visible by checking the title of ActionBar. Its not proper solution but it works for me perfectly. if u have any question tell me. Hope it works for u. Thanx
public interface OnBackPressedListener {
boolean onBackPressed();
}
public interface OnBackPressedNotifier {
void registerOnBackPressedListener(OnBackPressedListener listener);
void unregisterOnBackPressedListener(OnBackPressedListener listener);
}
public class SampleActivity extends Activity implements OnBackPressedNotifier {
List<OnBackPressedListener> onBackPressedListeners = new ArrayList<>();
#Override
public void registerOnBackPressedListener(OnBackPressedListener listener) {
if (!onBackPressedListeners.contains(listener))
onBackPressedListeners.add(listener);
}
#Override
public void unregisterOnBackPressedListener(OnBackPressedListener listener) {
if(onBackPressedListeners.contains(listener))
onBackPressedListeners.remove(listener);
}
#Override
protected void onDestroy() {
onBackPressedListeners.clear();
super.onDestroy();
}
private boolean notifyOnBackPressed(){
boolean handledByFragment = false;
for (OnBackPressedListener listener : onBackPressedListeners){
handledByFragment = listener.onBackPressed();
if (handledByFragment)
break;
}
return handledByFragment;
}
#Override
public void onBackPressed() {
if (!notifyOnBackPressed())
super.onBackPressed();
}
}
public class SampleFragment extends android.support.v4.app.Fragment implements OnBackPressedListener {
#Override
public void onAttach(Context context) {
super.onAttach(context);
((OnBackPressedNotifier)getActivity()).registerOnBackPressedListener(this);
}
#Override
public void onDetach() {
((OnBackPressedNotifier)getActivity()).unregisterOnBackPressedListener(this);
super.onDetach();
}
#Override
public boolean onBackPressed() {
// Handle onBackPressed
return false;
}
You can use the new API.
activity?.onBackPressedDispatcher?.addCallback
on a child layout (View) is there a callback for when the view is removed from it's parent? I need to recycle some images when the view is done. I've been looking around on the web for what to do, but haven't found anything helpful yet.
I've been looking for something like this too. The best I can find is View.OnAttachStateChangeListener. I doubt it's ideal, as it's the callback for when the View is added & removed from the Window - not the parent, but it's sufficient for my needs.
Instead of registering a new listener, you can override onDetachedFromWindow in your custom View code.
I fall in that trap what marmor said:)
#Override
protected void onDetachedFromWindow() { I want to do something here, sometimes called sometimes not!!}
protected void onAttachedToWindow() {It is working fine, always}
This code is in a CustomView.
The calling code is:
contentHolder.removeAllViews();
// ... init my CustomView ...
contentHolder.addView(myCustomView);
contentHolder.requestLayout();// useless, not need
contentHolder.invalidate();// useless, not need
To understand why is not working you have to go inside Android API:
public void removeAllViews() {
removeAllViewsInLayout();
requestLayout();
invalidate(true);
}
public void removeAllViewsInLayout() {
final int count = mChildrenCount;
if (count <= 0) {
return;
}
final View[] children = mChildren;
mChildrenCount = 0;
final View focused = mFocused;
final boolean detach = mAttachInfo != null;
boolean clearChildFocus = false;
needGlobalAttributesUpdate(false);
for (int i = count - 1; i >= 0; i--) {
final View view = children[i];
if (mTransition != null) {
mTransition.removeChild(this, view);
}
if (view == focused) {
view.unFocus(null);
clearChildFocus = true;
}
view.clearAccessibilityFocus();
cancelTouchTarget(view);
cancelHoverTarget(view);
if (view.getAnimation() != null ||
(mTransitioningViews != null && mTransitioningViews.contains(view))) {
addDisappearingView(view);
} else if (detach) {
view.dispatchDetachedFromWindow();
}
if (view.hasTransientState()) {
childHasTransientStateChanged(view, false);
}
dispatchViewRemoved(view);
view.mParent = null;
children[i] = null;
}
if (clearChildFocus) {
clearChildFocus(focused);
if (!rootViewRequestFocus()) {
notifyGlobalFocusCleared(focused);
}
}
}
The key is here:
if (view.getAnimation() != null ||
(mTransitioningViews != null && mTransitioningViews.contains(view))) {
So, if you have animation ( and in 1 case I have and in 9 cases not) it will not called the onDetachedFromWindow() and it will mess the whole UI :)
public void endViewTransition(View view) {
if (mTransitioningViews != null) {
mTransitioningViews.remove(view);
final ArrayList<View> disappearingChildren = mDisappearingChildren;
if (disappearingChildren != null && disappearingChildren.contains(view)) {
disappearingChildren.remove(view);
if (mVisibilityChangingChildren != null &&
mVisibilityChangingChildren.contains(view)) {
mVisibilityChangingChildren.remove(view);
} else {
if (view.mAttachInfo != null) {
view.dispatchDetachedFromWindow();
}
if (view.mParent != null) {
view.mParent = null;
}
}
invalidate();
}
}
}
Again in some cases will be called even with animation.
addDisappearingView(view);
The accepted answer suggest something like this:
addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
#Override
public void onViewAttachedToWindow(View v) {
}
#Override
public void onViewDetachedFromWindow(View v) {
System.out.println("MyCustomView.onViewDetachedFromWindow");
}
});
Sadly on animation will not print the desired text.
Some important code from android.view.ViewGroup API:
void dispatchViewRemoved(View child) {
onViewRemoved(child);
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewRemoved(this, child);
}
}
public void onViewRemoved(View child) {
}
So, you can override your RelativeLayout for this method.
My animation is an infinite animation, and it will not be called very soon any of the methods!
If you have an infinite animation the correct way is to write this code, when you call remove all views:
if(contentHolder.getChildCount() > 0 ){
View child0 = contentHolder.getChildAt(0);
Animation animation = child0.getAnimation();
if(animation != null) {
animation.cancel();
child0.clearAnimation();
}
}
contentHolder.removeAllViews();
Now it will be called the protected void onDetachedFromWindow()!
The Android KTX (Core KTX) library gives you a nice solution for this.
You'll need this dependency: androidx.core:core-ktx:1.3.0
You can then call a function "doOnDetach" to signal you want to run some code (once) when the view is removed from the window:
fun myInitCode() {
...
myView.doOnDetach(this::doOnMyViewDetachFromWindow)
...
}
fun doOnMyViewDetachFromWindow(view: View) {
... put your image cleanup code here ...
}
You can pass a lambda to "doOnDetach" but a method reference as shown above may be cleaner, depending on how much work you have to do.
The description of doOnDetach is as follows:
androidx.core.view ViewKt.class public inline fun View.doOnDetach(
crossinline action: (View) → Unit ): Unit
Performs the given action when this view is detached from a window. If
the view is not attached to a window the action will be performed
immediately, otherwise the action will be performed after the view is
detached from its current window. The action will only be invoked
once, and any listeners will then be removed.