欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android之事件处理

程序员文章站 2022-03-09 21:04:27
...

Android事件处理概述

Android事件处理机制

  • 基于监听的事件处理
    为Android界面组件绑定特定的事件监听器
    一般用于处理较为复杂的事件

  • 基于回调的事件处理
    重写Android组件特定的回调方法。
    一般用于处理具有通用性的事件

基于监听的事件处理

监听的处理模型

  • Event Source(事件源):事件发生的场所,通常是各个组件
 // 创建PlaneView
 final PlaneView planeView = new PlaneView(this);
 setContentView(planeView);
  • Event(事件):事件封装了界面组件上发生的特定事件(简单事件已经由系统封装好),通常是用户的操作。
// 为planeView组件的键盘事件绑定监听器
planeView.setOnKeyListener(onKeyListener);
  • Event Listener(事件监听器):负责监听事件源上所发生的事件,并对事件作出相应的响应。在监听事件处理模型中,事件监听器必须实现事件监听器接口,Android也为不同组件提供了不同的监听器接口。例如:View类点击、长按等事件。
 View.OnKeyListener onKeyListener = new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                // 获取由那个键触发的事件
                switch (event.getKeyCode()){
                    case KeyEvent.KEYCODE_S:  // 下移
                        planeView.currentY += speed;
                        break;
                    case KeyEvent.KEYCODE_W:  // 上移
                        planeView.currentY -= speed;
                        break;
                    case KeyEvent.KEYCODE_A:  // 左移
                        planeView.currentX -= speed;
                        break;
                    case KeyEvent.KEYCODE_D:  // 右移
                        planeView.currentX += speed;
                        break;


                }
                // 通知planeView组件重绘
                planeView.invalidate();
                return true;
            }
        }

事件处理方式

基于监听Android的事件处理机制是一种委派式事件处理方式:普通组件(事件源)将整个事件处理委托给特定的对象(事件监听器);当该事件源发生指定事件时,就通知所委托的事件监听器,由事件监听器来处理此事件。

基于回调的事件处理

事件处理方式

  • 基于回调的Android事件处理方式与监听的方式恰恰相反,对于基于回调的事件处理模型来说,事件源与监听器是统一的,当用户在某一组件上进行某一操作时所产生的事件由组件自己特定的方法进行处理,不再需要监听器类来进行监听处理。
  • 为了使用回调机制类处理组件上所发生的事件,需要为该组件提供对应事件处理方法,而Java又是一种静态语言,无法为某个对象动态添加方法,因此,只能继承组件类,重写该类的事件处理方法来实现对事件的处理。例如View中的onKeyDown、onKeyLongPress方法等。

基于回调的事件传播

  • 几乎所有基于回调的事件处理方法都有一个boolean类型的返回值,该返回值用于标识改处理方法是否能够完全处理该事件:

    1. 如果处理事件的回调方法返回true,表明该处理方法已完全处理该事件,该事件不会再传播出去。
    2. 如果处理事件的回调方法返回false,表明该处理方法并未完全处理该事件,该事件会传播出去。
  • 对于基于回调的事件传播而言,某组件上面发生的事件不仅会激发该组件上的回调方法,还会触发该组件所在Activity的回调方法—-只要事件能传播到改Activity。

示例1:

public class ButtonFalse extends Button{

    public ButtonFalse(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        this.setText("触摸ButtonFalse,未处理完事件,事件外传。");
        // 触摸时当前组件下只处理按下的事件
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            LogUtils.e("onTouchEvent", "ButtonFalse   =  ACTION_DOWN");
        }
        LogUtils.e("onTouchEvent", "未完全处理该事件,该事件依然向外传播");
        // 返回false,表明并未完全处理该事件,该事件依然向外传播
        return false;
    }

}
public class ButtonTrue extends Button {

    public ButtonTrue(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        this.setText("触摸ButtonTrue,处理完事件,事件不外传。");
        LogUtils.e("onTouchEvent", "已经完全处理该事件,该事件不会向外传播");
        // 返回true,表明已经完全处理该事件,该事件不会向外传播
        return true;
    }
}
public class EventTwoActivity extends AppCompatActivity {
    @BindView(R.id.btn_atvt_event_two_true)
    ButtonTrue btnAtvtEventTwoTrue;
    @BindView(R.id.btn_atvt_event_two_false)
    ButtonFalse btnAtvtEventTwoFalse;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_event_two);
        ButterKnife.bind(this);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 控件中触摸事件只处理了按下事件,并返回false,事件继续传播给了Activity,由Activity处理抬起的事件
        if (event.getAction() == KeyEvent.ACTION_UP) {
            LogUtils.e("onTouchEvent", "EventTwoActivity = ACTION_UP");
        }
        // 返回false表明该事件会向外传播
        return false;
    }
}
04-09 10:40:01.868 22038-22038/com.fpp.status E/onTouchEvent: 已经完全处理该事件,该事件不会向外传播 (ButtonTrue.java:36)
04-09 10:40:01.951 22038-22038/com.fpp.status E/onTouchEvent: 已经完全处理该事件,该事件不会向外传播 (ButtonTrue.java:36)
04-09 10:40:03.965 22038-22038/com.fpp.status E/onTouchEvent: ButtonFalse   =  ACTION_DOWN (ButtonFalse.java:39)
04-09 10:40:03.965 22038-22038/com.fpp.status E/onTouchEvent: 未完全处理该事件,该事件依然向外传播 (ButtonFalse.java:41)
04-09 10:40:04.012 22038-22038/com.fpp.status E/onTouchEvent: EventTwoActivity = ACTION_UP (EventTwoActivity.java:64)

示例2:

public class DrawView extends View {
    public float currentX = 40;
    public float currentY = 50;
    // 定义画笔
    Paint p = new Paint();
    public DrawView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 设置画笔颜色
        p.setColor(Color.RED);
        // 绘制圆
        canvas.drawCircle(currentX,currentY,15,p);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 设置view位置
        this.currentX = event.getX();
        this.currentY = event.getY();
        // 重绘
        this.invalidate();
        return true;
    }
}
    <com.fpp.status.activity.three.eight.DrawView
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

布局中定义好了自定义View,在运行之后,屏幕上面进行触摸移动,则圆点会根据手指移动,在自定义View时,已经处理了跟随移动的事件。

响应系统设置的事件

Configuration类简介

Configuration类专门用于描述手机设备上的配置信息,主要包含用户特定配置项,系统动态设备配置。

获取手机配置和系统配置

tvScreenDirection.setText(cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ? "横向屏幕" : "竖向屏幕");
                tvPhoneDirection.setText(cfg.orientation == Configuration.NAVIGATION_NONAV ?
                        "没有方向控制" : cfg.orientation == Configuration.NAVIGATION_WHEEL ?
                        "滚轮控制方向" : cfg.orientation == Configuration.NAVIGATION_DPAD ? "方向键控制方向" : "轨迹球控制方向");
                tvTouchScreen.setText(cfg.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH ? "无触摸屏" : "支持触摸屏");
                tvNetwork.setText("国家码 = " + cfg.mcc + "    网络码 = " + cfg.mnc + "");

系统设置更改

case R.id.btn_atvt_configuration_change:
                // 如果是横屏
                if (cfg.orientation == Configuration.ORIENTATION_LANDSCAPE){
                    // 设置竖屏
                    ConfigurationActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
                }
                // 如果是竖屏
                if (cfg.orientation == Configuration.ORIENTATION_PORTRAIT){
                    // 设置横屏
                    ConfigurationActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
                }

                break;
 @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        LogUtils.e("onConfigurationChanged","系统屏幕发生变化" + "  \n 修改后屏幕方向 = " +
                (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ? "横向屏幕" : "竖向屏幕"));
    }
<!-- 设置Activity可以监听屏幕方向改变的事件 -->
        <activity
            android:configChanges="orientation|screenSize"
            android:name=".activity.three.seven.ConfigurationActivity"/>

Handler消息传递机制

Handler类简介

  • 常用方法

    1. void handleMessage(Message msg):处理消息的方法
    2. final boolean hasMessages(int what):检查消息队列中是否包含what属性为指定值的消息
    3. final boolean hasMessages(int what , Object obj):检查消息队列中是否包含what属性为指定值的消息且obj属性为指定对象的消息
    4. 多个重载的Message obtainMessage():获取消息
    5. sendEmptyMessage(int what):发送空消息
    6. final boolean sendEmptyMessageDelayed(int what , long delayMillis):指定delayMillis毫秒之后发送空消息
    7. final boolean sendMessage(Message msg):立即发送消息
    8. final boolean sendMessageDelayed(Message msg , long delayMillis):指定delayMillis毫秒之后发送消息
  • 作用:

    1. 在新启动的线程中发送消息
    2. 在主线程中获取、处理消息
  • 示例:

    int[] imageIds = new int[]{
            R.drawable.guide_01,
            R.drawable.guide_02,
            R.drawable.guide_03
    };
    int currentImageId = 0;
    private void initView() {
        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // 2、如果消息是本程序所发送的
                if (msg.what == 1001) {
                    // 3、动态修改所要显示的图片
                    ivAtvtHandlerTest.setImageResource(imageIds[currentImageId++ % imageIds.length]);
                }
            }
        };

        // 1、开启新线程设定指定时间间隔,发送消息给主线程
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                // 发送空消息
                handler.sendEmptyMessage(1001);
            }
        }, 0, 1000);  // 0:开始时间  1000:时间间隔

    }

Handler、Loop、MessageQueue工作原理

  • 简介
    1. Message:Handler接收和处理的消息对象
    2. Looper:每个线程只有一个Looper,它的loop方法负责读取MessageQueue中的消息,读到消息之后就把消息交给发送消息的Handler进行处理
    3. MessageQueue:消息队列,采用先进先出的方式来管理Message,程序创建Looper对象时,会在它的构造器中创建MessageQueue对象。
  • 作用
    1. Looper:每个线程只有一个Looper,它负责管理MessageQueue,会不断的从MessageQueue中取出消息,并将消息分给对应的Handler处理。
    2. MessageQueue:由Looper负责管理,采用先进先出的方式管理Message。
    3. Handler :它能把消息发送给Looper管理的MessageQueue,并负责处理Looper分给它的消息。
  • 步骤
    1. 调用Looper的prepare()方法为当前线程创建Looper对象,创建Looper对象时,它的构造器会创建与之配套的MessageQueue。
    2. 有了Looper之后,创建Handler子类的实例,重写handleMessage()方法,负责处理来自其它线程的消息。
    3. 调用Looper的loop()方法启动Looper。
  • 实例:
    boolean isMoves = true;
    public static final String UPPER_NUM = "upper";
    private Handler mHandler;
    class ChildThread extends Thread {
        @Override
        public void run() {
            Log.e(TAG, "run: isMoves = " + isMoves);
            while (isMoves) {
                // 1-1、创建Looper对象
                Looper.prepare();
                // 1-2、实例化Handler对象,并接收消息,处理消息
                mHandler = new Handler() {
                    // 处理消息
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        // 主线程接收到子线程发送的消息,处理UI
                        if (msg.what == 1002) {
                            int upper = msg.getData().getInt(UPPER_NUM);
                            List<Integer> nums = new ArrayList<Integer>();
                            // 计算从2开始到upper的所有质数
                            outer:
                            for (int i = 2; i <= upper; i++) {
                                // 用i除以从2开始、到i的平方根的所有数
                                for (int j = 2; j <= Math.sqrt(i); j++) {
                                    // 如果可以整除,则表明这个数不是质数
                                    if (i != 2 && i % j == 0) {
                                        continue outer;
                                    }
                                }
                                nums.add(i);
                            }

                            LogUtils.e("显示所有的质数 ", " " + nums.toString());

                        }

                    }
                };
                // 1-3、启动取出消息方法,并将消息分发给对应的Handler进行处理
                Looper.loop();

            }
        }
    }
    private ChildThread mChildThread;
    private void initView() {
        // 2-1、初始化子线程
        mChildThread = new ChildThread();
        // 2-2、启动子线程
        mChildThread.start();
    }
    @OnClick(R.id.tv_atvt_handler)
    public void onViewClicked() {
        LogUtils.e("--------------------");
        // 3-1、创建消息
        Message msg = new Message();
        msg.what = 1002;
        Bundle bundle = new Bundle();
        bundle.putInt(UPPER_NUM, Integer.parseInt(etAtvtHandler.getText().toString()));
        msg.setData(bundle);
        // 3-2、向新线程中的Handler发送消息
        mHandler.sendMessage(msg);
    }

异步任务