Android Scroll 滑动效果 及 触摸事件处理

41 篇文章 2 订阅
27 篇文章 0 订阅

Android Scroll 滑动效果 及 触摸事件处理

跟着《安卓群英传》看的,很多知识点在书上,这里就写一些小demo就好了,以后复习

一、TouchEvent实现滑动——小球跟着手指走

1.新建一个类继承自view,并覆写onDraw()方法
package com.example.toucheventactivity.Bean;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.view.inputmethod.CursorAnchorInfo;

import androidx.annotation.Nullable;

public class TestView extends View {
    int x, y;

    public TestView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public void setXY(int _x, int _y) {
        x = _x;
        y = _y;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawColor(Color.CYAN);
        Paint paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setAntiAlias(false);
        canvas.drawCircle(x, y, 40, paint);
        paint.setColor(Color.WHITE);
        canvas.drawCircle(x-8,y-8,8,paint);
    }
}

2.把testview加载到主活动上
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <com.example.toucheventactivity.Bean.TestView
        android:id="@+id/testview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
3.主活动java文件下编辑代码
package com.example.toucheventactivity;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

import com.example.toucheventactivity.Bean.TestView;

public class MainActivity extends AppCompatActivity {

    private TestView mTestView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTestView = findViewById(R.id.testview);
        mTestView.setOnTouchListener(new mOnTouch());
    }

    //处理触摸事件
    private class mOnTouch implements View.OnTouchListener {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            int x1, y1;
            x1 = (int) event.getX();
            y1 = (int) event.getY();

            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mTestView.setXY(x1, y1);
                    //执行刷新操作
                    mTestView.invalidate();
                    break;
                case MotionEvent.ACTION_UP:
                    Toast.makeText(MainActivity.this,"您抬起了手指",Toast.LENGTH_SHORT).show();
                    break;
                case MotionEvent.ACTION_MOVE:
                    mTestView.setXY(x1,y1);
                    mTestView.invalidate();
                    break;
                case MotionEvent.ACTION_CANCEL:
                    Toast.makeText(MainActivity.this,"您取消了触碰",Toast.LENGTH_SHORT).show();
                    break;
            }
            return true;
        }
    }
}
4.运行效果:
image-20210616202832638

二、多点触控

  • Android的多点触控功能需要运行在Android2.0版本以上
  • 支持多点触控,可以在屏幕上同时使用三个手指完成缩放、旋转或者任何使用多点触控想做的事情;
  • 多点触控和单点触控的基本原理是一直的。当手指触控屏幕是,MotionEvent对象被创建,并且被传递到前面介绍的方法中
  • 在一个手势中,可以使用getPointerId()方法获得出触碰点id,用来在后续的触摸事件中跟踪手指。发生一系列的动作后,可以使用**findPointerIndex()**方法找到触点I当前对应的触点索引,然后使用触点索引获取触摸事件的信息。
1.使用
package com.example.toucheventactivity;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;

public class MultiActionActivity extends AppCompatActivity {

    private static final String TAG = "MultiActionActivity";
    private int pointerId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_multi_action);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //得出触碰点id
        pointerId = event.getPointerId(0);

        //找到当前触碰点的索引
        int pointerIndex = event.findPointerIndex(pointerId);

        //触碰点的xy坐标
        float x = event.getX(pointerIndex);
        float y = event.getY(pointerIndex);

        //大于一表明有多个点进行了触发
        if (event.getPointerCount() > 1) {
            for (int i = 0; i < event.getPointerCount(); i++) {
                pointerIndex = event.findPointerIndex(i);
                x = event.getX(pointerIndex);
                y = event.getY(pointerIndex);
                Log.d(TAG, "MulitiTouch Event,The Point + " + i + ":" + "x:" + x + "," + y);
            }
        } else {
            Log.d(TAG, "Single Touch Event: ->  " + x + "," + y);
        }
        return true;
    }

    public static String actionToString(int action) {
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                return "Down";
            case MotionEvent.ACTION_MOVE:
                return "Move";
            case MotionEvent.ACTION_CANCEL:
                return "cancel";
            case MotionEvent.ACTION_UP:
                return "up";
            case MotionEvent.ACTION_POINTER_UP:
                return "Pointer Up";
            case MotionEvent.ACTION_OUTSIDE:
                return "Outside";
            case MotionEvent.ACTION_POINTER_DOWN:
                return "Point Down";
        }
        return "";
    }
}

三、手势

  • 手势也是一组触摸事件的序列,由基本触摸事件的动作组成。
  • 手势可以是简单的触摸事件序列,例如单击、滑屏等,也可以是自定义更复杂的触摸事件序列。基本的手势包括单击、长按、滑动、拖动、双击、缩放操作。
  • 每种手势都是用户的一种特定动作,触摸屏可以识别这些动作完成相应的功能。滑动就是手指在屏幕上拖动一个物体,快速地朝一个方向移动,然后抬起。
  • Android提供了GestureDetector类检测的一些常见手势,其中的方法包括onDown()、 onLongPress()和onFling()等。另外,使用scaleGestureDetector类来实现缩放手势。
  • 当初始化GestureDetector对象时,需要传入一个实现了OnGestureListener接口的参数,当一个特定的手势被识别时,就会执行OnGestureListener中的各种手势的处理方法。
  • 为了使GestureDetector对象能够接收触摸事件,需要覆盖View或Activity的onTouchEvent()方法,并将所有的触摸事件传递到GestureDetector对象中。
  • 如果只想处理部分手势,可以继承SimpleOnGestureListener。
  • 缩放手势有两种操作:一种是两个手指同时触摸屏幕,向相互远离的方向移动,然后同时离开屏幕,这是放大操作;另一种是两个手指同时触摸屏幕,向相互靠近的方向移动,然后同时离开屏幕,这是缩小操作。
1.实例:缩放一个图片
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity"
    android:background="#27B1B1">

    <com.example.toucheventactivity.Image.ScaleImageView
        android:src="@drawable/ic_launcher_foreground"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="matrix"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

new一个类继承自AppCompatImageView,里面用来处理手势操作的主要逻辑,注释很详细了可以看着注释回忆。

package com.example.toucheventactivity.Image;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.widget.AppCompatImageView;

@RequiresApi(api = Build.VERSION_CODES.M)
public class ScaleImageView extends AppCompatImageView implements ScaleGestureDetector.OnScaleGestureListener, View.OnTouchListener {

    ScaleGestureDetector mScaleGestureDetector;

    Matrix mScaleMatrix = new Matrix();
    //当前的缩放度
    float initScale = 1.0f;
    //最大的缩放是6倍
    static final float SCALE_MAX = 6.0f;

    float[] matrixValues = new float[9];

    public ScaleImageView(@NonNull @org.jetbrains.annotations.NotNull Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs) {
        super(context, attrs);
        mScaleGestureDetector = new ScaleGestureDetector(context, this);
        this.setOnTouchListener(this);
    }


    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        float scale = getScale();
        //检测到基于原来的缩放还有一个新的缩放尺度
        float scaleFactor = detector.getScaleFactor();

        //不能比最大的缩放还要大,比最小的还要小
        if ((scale < SCALE_MAX && scaleFactor > 1.0f) || (scale > initScale && scaleFactor < 1.0f)) {
            if ((scaleFactor * scale < initScale)) {
                //放大倍数是初始缩放和当前缩放的一个比值
                scaleFactor = initScale / scale;
            }
            if (scale * scaleFactor > SCALE_MAX) {
                scaleFactor = SCALE_MAX / scale;
            }
        }

//        mScaleMatrix.postScale(scaleFactor,scaleFactor,getWidth()/2,getHeight()/2);
        //以手势聚焦点为中心点进行缩放
        mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX() / 2, detector.getFocusY() / 2);

        //检查一下边界,是否有白边之类的
        checkBorderAndCenterWhenScale();

        //保存图片的matrix
        setImageMatrix(mScaleMatrix);

        return true;
    }

    private void checkBorderAndCenterWhenScale() {
        //得到当前图片的绘制区域
        RectF rectF = getMatrixRecF();
        //移动的空间
        float deltaX = 0;
        float deltaY = 0;

        int width = getWidth();
        int height = getHeight();

        if (rectF.width() >= width) {
            if (rectF.left > 0) {
                deltaX = - rectF.left;
            }
            if ((rectF.right < width)) {
                deltaX = width - rectF.right;
            }
        }

        if (rectF.height() >= height) {
            //说明图片与顶部有空白区域,需要向上调整
            if (rectF.top > 0) {
                deltaY = - rectF.top;
            }
            //说明与底部有空白,需要向下调整
            if (rectF.bottom < height) {
                deltaY = height - rectF.bottom;
            }
        }

        //移动的具体方法
        if (rectF.width()<width) {
            deltaX = width*0.5f - rectF.right+0.5f*rectF.width();
        }
        if(rectF.height()<height){
            deltaY = height*0.5f-rectF.bottom+0.5f*rectF.height();
        }
        mScaleMatrix.postTranslate(deltaX,deltaY);
    }

    private float getScale() {
        mScaleMatrix.getValues(matrixValues);
        return matrixValues[Matrix.MSCALE_X];
    }

    //得到当前绘图区域的边界
    private RectF getMatrixRecF() {
        Matrix matrix = mScaleMatrix;
        RectF rectF = new RectF();
        //开始绘制
        Drawable d = getDrawable();
        //如果不为空,则开始绘制
        if (d != null) {
            rectF.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
        }
        return rectF;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {

    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        mScaleGestureDetector.onTouchEvent(event);
        return true;
    }
}
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

四、拖放操作

  • Android应用程序在处理拖放操作时,可以通过实现View.OnDragListener接口,创建拖动事件监听器;然后通过View类提供的setOnDragListener()方法,为View对象设置一个拖动事件监听器对象。每个View对象都可以有一个onDragEvent()回调方法。
  • 应用程序通过调用View.OnDragListener的startDrag()方法告诉系统开始一个拖动,也就是告诉系统可以开始发送拖动事件了。一旦应用程序调用startDrag()方法,剩下的过程就是使用系统发送给布局中的视图对象的事件。
  • 拖放过程的四个基本步骤或状态:
    • 开始
    • 继续
    • 释放
    • 终止
  • 拖放事件:
    • 用户界面的视图对象通过实现View.OnDragListener接口的拖动事件监听器,或通过它自身的onDragEvent(DragEvent)回调方法来接收拖动事件。当系统回调这个方法或监听器时,会传递给它们一个拖动事件的对象。
    • 在Android系统中,使用DragEvent类来描述拖动事件。
1,设计拖动操作:
  • 开始拖动:

    • 用户用一个拖动的手势开始一个拖动,通常是一个在视图对象上的长按动作。作为回应,需要做下面两件事情。
    • (1)为要移动的数据创建一个clipData和ClipData.Iltem对象。
    • (2)创建拖动阴影。
  • 响应拖动开始事件:

    • 在拖动过程中,系统将拖动事件传递给当前布局中的视图对象的拖动事件监听器。监听器应该调用getAction()方法获取操作类型。在一个拖动开始时,该方法返回ACTION_DRAG_STARTED。

    • 当ACTION_DRAG_STARTED事件发生时,监听器需要进行下面的处理:

    • (1)调用getClipDescription()方法获取ClipDescription。使用在ClipDescription 中的MIME类型的方法查看监听器是否接收被拖动的数据。如果拖放操作没有数据移动,这个步骤就不是必需的。

    • (2)如果监听器可以接收一个拖动事件,它必须返回true。

  • 在拖动过程中处理事件:

    • 在拖动过程中,当监听器对ACTION_DRAG_STARTED拖动事件的返回值为true时,监听器继续接收后续的拖动事件。
    • 在拖动过程中,getAction()返回的事件类型包括三个:
        1. ACTION_DRAG_ENTERED
        1. ACTION_DRAG_LOCATION
        1. ACTION_DRAG_EXITED
  • 响应释放动作:

    • 当用户在某个视图上释放拖动阴影时,该视图会预先报告是否可以接收被拖动的内容,系统会将拖动事件分发给具有ACTION_DROP操作类型的视图。
    • 监听器在事件处理时,需要做两件事。
      • (1)调用getClipData()方法获取最初在startDrag()方法中应用的clipData对象,并存储。如果拖放操作没有数据的移动,就不必进行该操作。
      • (2)如果释放动作已顺利完成,监听器应返回true;如果没有完成,则返回false。这个被返回的值成为ACTION_DRAG_ENDED事件中getResult()方法的返回值。
2.实现拖放操作的步骤:
  • 实现拖放操作的步骤共有六步,包括创建应用程序可以根据实际的情况册减步骤:
    • 1.定义XML绘制图片;
    • 2.定义布局等资源文件;
    • 3.创建或者打开Activity,获取定义的视图对象;
    • 4.定义或实现TouchListener;
    • 5.定义或实现DragListener;
    • 6.将监听器注册到视图对象。
3.实例:图片跟着手指长按进行拖动,拖动到屏幕下方时颜色改变并且显示坐标
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context=".MainActivity2"
    android:id="@+id/topContainer">

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

    <LinearLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_alignParentBottom="true"
        android:orientation="vertical"
        android:background="#ccc">
        <TextView
            android:id="@+id/title"
            android:textColor="#f00"
            android:textSize="18sp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>


</RelativeLayout>
package com.example.toucheventactivity;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;

import android.content.ClipData;
import android.content.ClipDescription;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.DragEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class MainActivity2 extends AppCompatActivity {

    static final String IMAGEVIEW_TAG = "已经拖到了目标区域";

    ImageView imageView;
    LinearLayout mContainer;
    RelativeLayout mTopContainer;
    TextView mTitle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        imageView = findViewById(R.id.img);
        imageView.setTag(IMAGEVIEW_TAG);
        mContainer = findViewById(R.id.container);
        mTitle = findViewById(R.id.title);
        mTopContainer = findViewById(R.id.topContainer);

        imageView.setOnLongClickListener(new View.OnLongClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.N)
            @Override
            public boolean onLongClick(View v) {

                //设置一个剪切板
                ClipData.Item item = new ClipData.Item((String) v.getTag());
                //设置剪切板里面的内容
                ClipData data = new ClipData(IMAGEVIEW_TAG, new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}, item);

                //开始拖动时,传入数据,并设置一个影子
                /**
                 * new View.DragShadowBuilder(v)
                 * 就是在给该视图设置一个阴影
                 */
                v.startDragAndDrop(data, new View.DragShadowBuilder(v), null, 0);

                return true;
            }
        });

        //当拖放的对象拖到了mTopContainer之后,就会执行该方法
        mTopContainer.setOnDragListener(new View.OnDragListener() {
            @Override
            public boolean onDrag(View v, DragEvent event) {

                int action = event.getAction();
                switch (action) {
                    //开始拖动的时候
                    case DragEvent.ACTION_DRAG_STARTED:
                        return true;
                    //进入的时候
                    case DragEvent.ACTION_DRAG_ENTERED:
                        return true;
                    //定位的时候
                    case DragEvent.ACTION_DRAG_LOCATION:
                        return true;
                    //离开的时候
                    case DragEvent.ACTION_DRAG_EXITED:
                        mTitle.setText("");
                        return true;
                    //释放的时候
                    case DragEvent.ACTION_DROP:
                        imageView.setX(event.getX() - imageView.getWidth() / 2);
                        imageView.setY(event.getY() - imageView.getHeight() / 2);
                        return true;
                    //当你拖放结束的时候
                    case DragEvent.ACTION_DRAG_ENDED:
                        return true;
                }
                return false;
            }
        });


        mContainer.setOnDragListener(new View.OnDragListener() {
            @Override
            public boolean onDrag(View v, DragEvent event) {
                int action = event.getAction();
                switch (action) {
                    //开始拖动的时候
                    case DragEvent.ACTION_DRAG_STARTED:
                        if (event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){
                            return true;
                        }
                        return false;
                    //进入的时候
                    case DragEvent.ACTION_DRAG_ENTERED:
                        mContainer.setBackgroundColor(Color.YELLOW);
                        return true;
                    //定位的时候
                    case DragEvent.ACTION_DRAG_LOCATION:
                        return true;
                    //离开的时候
                    case DragEvent.ACTION_DRAG_EXITED:
                        mContainer.setBackgroundColor(Color.BLUE);
                        return true;
                    //释放的时候
                    case DragEvent.ACTION_DROP:
                        //释放的时候,将坐标显示到TextView上面
                        ClipData.Item item = event.getClipData().getItemAt(0);
                        String dragData = item.getText().toString();
                        mTitle.setText(dragData+"  "+ event.getX()+":"+event.getY());
                        return true;
                    //当你拖放结束的时候
                    case DragEvent.ACTION_DRAG_ENDED:
                        return true;
                }
                return false;
            }
        });
    }
}

注释很清楚了,自己看注释就好了。
在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值