Andrey Tsarev
Andrey Tsarev

Reputation: 779

onTouch and onClick not triggering properly

I have ReltiveLayout with and ImageView and a TextView inside.

So I have two event listeners:

I am using 9patch for the image. When I use the onTouch event onClick is not firing at all and also onTouch is not working very good. If I disable onTouch and leave onClick it fires and changes the Activity. I want to make onTouch and onClick work together. I want:

Heres my code:

StartBtn = (RelativeLayout) findViewById(R.id.StartBtn);
StartBtnImage = (ImageView) findViewById(R.id.StartBtnImage);
StartBtnText = (TextView) findViewById(R.id.StartBtnText);

StartBtn.setOnTouchListener(new TouchButton(StartBtnImage)); //Commenting this line makes the onClickListener work
StartBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        StartBtnImage.setImageResource(R.drawable.btn_selected);
        Log.d("ButtonClick", "Done");
        Intent myIntent = new Intent(MainMenu.this, Game.class);
        startActivity(myIntent);

    }
});

And my TouchButton class:

public class TouchButton implements View.OnTouchListener {

    ImageView IV;

    public TouchButton(ImageView Image) {
        IV = Image;
    }

    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                IV.setImageResource(R.drawable.btn_selected);
                return true;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_OUTSIDE:
            default:
                IV.setImageResource(R.drawable.btn);
                return false;
        }
    }
}

Here is my layout

<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" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainMenu">

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/MainMenuBg"
    android:background="@drawable/main_menu_bg" />

<RelativeLayout
    android:layout_width="250dp"
    android:layout_height="75dp"
    android:layout_alignParentTop="true"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="130dp"
    android:id="@+id/StartBtn"
    android:clickable="true">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/StartBtnImage"
        android:src="@drawable/btn" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="Start"
        android:id="@+id/StartBtnText"
        android:layout_centerVertical="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:singleLine="false"
        android:gravity="center|center_vertical|center_horizontal"
        android:textSize="50dp" />
</RelativeLayout>

I know I'm bad at explaining, so here is a video: https://www.youtube.com/watch?v=PtdUsW-Dnqk

Upvotes: 2

Views: 6365

Answers (4)

Sukhendu Sadhukhan
Sukhendu Sadhukhan

Reputation: 15

Just set

    @Override
    public boolean onDown(MotionEvent e) {
        return false;
    }

Your both touchListener and clickListener will work fine

Upvotes: 0

Here is my code, I hope this can solve your problem ` public boolean onTouch(View v, MotionEvent event) {

                int index = event.getActionIndex();
                int action = event.getActionMasked();
                int pointerId = event.getPointerId(index);
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        if (mVelocityTracker == null) {
                            // Retrieve a new VelocityTracker object to watch the
                            // velocity of a motion.
                            mVelocityTracker = VelocityTracker.obtain();
                            //getrawX, getrawY
                            startX = event.getRawX();
                            startY = event.getRawY();
                            // Add a user's movement to the tracker.
                            mVelocityTracker.addMovement(event);
                        } else {
                            // Reset the velocity tracker back to its initial state.
                            mVelocityTracker.clear();
                        }
                        break;
                    case MotionEvent.ACTION_MOVE:
                        mVelocityTracker.addMovement(event);
                        mVelocityTracker.computeCurrentVelocity(1000);
                        velocityX = mVelocityTracker.getXVelocity(pointerId);
                        velocityY = mVelocityTracker.getXVelocity(pointerId);
                        diffX = event.getRawX() - startX;
                        diffY = event.getRawY() - startY;
                        if(Math.abs(diffX)> 10) {
                            startX = event.getRawX();
                            if (diffX < 0 && velocityX != 0) {
                                swipeLeft(diffX, velocityX);
                            } else {
                                swipeRight(diffX, velocityX);
                            }
                        }
                        if(Math.abs(diffY)> 10) {
                            startY = event.getRawY();
                            if (diffY < 0 && velocityY != 0) {
                                swipeTop(diffY, velocityY);
                            } else {
                                swipeBottom(Math.abs(diffY) / velocityY);
                            }
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                        if(distanceX == 0){
                            Intent intent = new Intent(context, MainActivity.class);
                            intent.putExtra("SET_ID", listSet.get(getAdapterPosition()).getId());
                            itemView.getContext().startActivity(intent);
                            break;
                        }
                    case MotionEvent.ACTION_CANCEL:
                        checkDis();
                        // Return a VelocityTracker object back to be re-used by others.
                        try {
                            mVelocityTracker.recycle();
                            mVelocityTracker = null;
                        }catch (Exception e){
                            Log.d("exeption", e.getMessage());
                        }
                        break;
                }
                return true;
            }
        });
    }
    // check action up then cancel
    //purpose: scroll to initalize status if the behind layout not been opened
    private void checkDis(){
        if(distanceX<=0 && distanceX > -200){
            scrollView(cvSetItem, 0,(long) Math.abs(distanceX)*600/150);
            distanceX = 0;
        }

    }
    private void scrollView( View v, float toX, long duration){
            // do something
    }
    private void swipeLeft(float diff, float velo) {
        distanceX += diff;
        Log.d("distance left", " " + distanceX);
        if(distanceX>-200 && distanceX <0) {
            scrollView(cvSetItem,distanceX, (long) (diff/velo));
        }else {
            distanceX = -200;
            scrollView(cvSetItem,distanceX, 0);
        }
    }
    private void swipeRight(float diff, float velo) {
        Log.d("distance right", " " + distanceX);
        distanceX+=diff;
        if(distanceX<0){
            scrollView(cvSetItem,distanceX, (long) Math.abs(diff/velo));
        }else {
            distanceX =0;
        }
    }

    private void swipeTop(float diffY, float velo) {

    }
    private void swipeBottom(float duration) {

    }`

Upvotes: 0

gosp
gosp

Reputation: 76

Yes you can use multiple listeners on a single view (if it was not possible it would have been problematic).

Basically, the touch event of your view is triggered before click event. If the touch listener consumes the event (returning true from onTouch) then the click listener won't be called. If you return true from the onTouch listener, then you don't consume the event (returning false) and any underlying listeners will be called like the onClick listener.

Here an example of two listeners attached to a single view (a button) :

    btnStart = (Button) findViewById(R.id.btn_start);

    btnStart.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            Log.d("stack", "TOUCH EVENT");
            return false;
        }
    });

    btnStart.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.d("stack", "CLICK EVENT");
            Toast.makeText(getApplicationContext(), "start game...", Toast.LENGTH_LONG).show();
        }
    });

and the layout, a simple button :

<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" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

<Button
    android:id="@+id/btn_start"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="start btn"/>
</RelativeLayout>

In the log you will see TOUCH EVENT written before CLICK EVENT when you touch the button because the onTouch listener does not consume the event (returns false) and get called first.

I suggest you to read doc about touch propagation in Android and check this usefull video on this subject by Dave Smith.

Finally You should have a look at widget states in xml for changing apparence of them when they get clicked, or focused, ... Have a look there

Hope it will help.

Upvotes: 4

reebow
reebow

Reputation: 575

UPDATE: Since you cannot use multiple listener on a single view see link. You could implement the behavior of your .setOnClickListener in your TouchButton.class.

public class TouchButton implements View.OnTouchListener {

ImageView IV;

public TouchButton(ImageView Image) {
    IV = Image;
}

public boolean onTouch(View v, MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            IV.setImageResource(R.drawable.btn_selected);
            return true;
        case MotionEvent.ACTION_UP:
            switch(v.getId()){
                case R.id.StartBtn:
                   IV.setImageResource(R.drawable.btn_selected);
                   Log.d("ButtonClick", "Done");
                   Intent myIntent = new Intent(MainMenu.this, Game.class);
                   startActivity(myIntent);
                   break;
             return true;
        case MotionEvent.ACTION_OUTSIDE:
        default:
            IV.setImageResource(R.drawable.btn);
            return false;
    }
  }
}

Upvotes: 2

Related Questions