Android进阶:网络与数据存储—步骤1:Android网络与通信(第2小节:Handler)

内容概览

  1. Handler是什么
  2. 为什么要使用Handler
  3. Handler/Looper/MessageQueue/Message
  4. Handler如何去实现(三种实现:1、下载文件并更新进度条 2、倒计时 3、打地鼠的游戏实现)
  5. 工作原理
  6. 如果更好的使用
  7. 扩展及总结

一、课程背景:

1、UI线程/主线程/AtivityThread

一个应用启动会开启一个主进程,接着这主进程会开启一个主线程,所有东西会开始在主线程中运作

主线程也叫做UI线程,为什么?因为主线程主要负责处理我们UI界面所有消息相关的分发

2、线程不安全

线程安全不安全针对的事多线程的。

假如有个控件是button,多个线程都来更新这个button,这个button的状态会不会混乱

线程安全就是button处理了这种多线程的状态,给button加锁,无论你哪个线程来方法都可以来访问,这就是线程安全,但加锁又导致性能的下降

线程不安全:没有针对多线程进行特别处理;UI线程是线程不安全的

3、消息循环机制(处理各种UI事件)

什么意思?

也就是说我一进入主线程,就开始进入循环,这个循环是一个死循环,用来处理消息、处理操作

当用户在UI线程点了Button按钮,它立马将这个消息发给这个循环,这个循环就开始处理

应用场景:

1.定时任务(消息和可执行的对象在未来的一段时间内执行)

2.线程和线程之间的处理(A线程到B线程中执行某个动作)

二、Handler相关概念简介

1.Handler

2.Looper(循环者)

3.Message

4.MessageQueue(消息队列)

寓意:Looper相当一个循环抽水机,MessageQueue相当于水井,message相当于水井里的水

通过Looper不断的抽出来到handlerMessage再交给handler处理者直接执行run;

  • 每个线程里面都有它单独的Looper和MessageQueue
  • 主线程默认创建了Looper和MessageQueue
  • 子线程不会默认创建;创建方式如下
//子线程不默认开始Looper和MessageQueue
     new Thread(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    //子线程开启的方式
                                    //准备好Looper
                                    Looper.prepare();
                                    Handler handler =new Handler();
                                    //让它开始循环
                                    Looper.loop();
                                    Thread.sleep(1000);
                                    
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                        });

 

三、使用Handler的实现和优化

3-1代码实现最简单的Handler

1.Handler.sendMessage();

2.Handler.post();

首先,在主线程中新建一个handler对象实现他的sendMessage()方法,在这个方法里面进行接受处理子线程传来的更新UI的操作

子线程中进行耗时的操作,同时也想进行更新UI,就通过sendEmptyMessage();方法来将消息传递给主线程的handler来处理

handler.sendEmptyMessage(1001);//1001是这个消息的代号

主线程:

  //创建一个handler对象
        //实现它的handlerMessage方法
        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //处理消息
                Log.d(TAG, "handlerMessage:" + msg.what);

                /**
                 *主线程接到子线程发出来的消息,处理
                 */
                if (msg.what == 1001)
                    //执行刷新UI操作
                    textView.setText("我是代号1001的子线程消息通过子线程发到主线程中处理");

            }
        };

子线程:

 //给button设置监听事件
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //可能要做大量耗时操作
                /**
                 * 子线程
                 */
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(3000);//子线程休眠3秒,模仿耗时操作
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        /**
                         * 通知UI线程更新
                         */
                        handler.sendEmptyMessage(1001);

                    }
                }
                ).start();
            }
        });

效果:

3-2Handler常见的发送消息方法

Handler.sendMessage(里面装的是一个消息);message消息进行打包,通过sendMessage()将消息发给主线程

    //使用sendMessage()方法前的消息要打包
                        Message message=Message.obtain();
                        message.what = 1002;//消息编号
                        message.arg1 = 1003;
                        message.arg2 = 1004;
                        message.obj =MainActivity.this;//当前对象
                        handler.sendMessage(message);

主线程相当于收到一个打包好的快递,进行拆包

  //创建一个handler对象
        //实现它的handlerMessage方法
        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                /**
                 *主线程接到子线程发出来的消息,处理
                 */
                if(msg.what ==1002){//拆包
                    Log.d(TAG, "handlerMessage:" + msg.what);
                    Log.d(TAG, "handlerMessage:" + msg.arg1);
                    Log.d(TAG, "handlerMessage:" + msg.arg2);
                    Log.d(TAG, "handlerMessage:" + msg.obj);
                    textView.setText("代号1002消息通过子线程sendMessage方法发到主线程处理");
                }
            }
        };

定时任务:

  1. 定时发送sendMessageAtTime();
  2. 延迟发送sendMessageDelayed(); 
    //定时发送消息,时间是绝对的
     handler.sendMessageAtTime(message,SystemClock.uptimeMillis()+3000);
    //延迟发送消息,时间是相对的
     handler.sendMessageDelayed(message,2000);
                        

  post();方法 它可以直接在run函数中执行你想要做的事情(比如更新UI),他也有postDelayed()和postAtTime()方法

//提交消息,可以直接在run里面做你想做的事情
//更新UI操作转到主线程进行

                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                int a=1+2+3;
                                TextView.setText("xxxxx");
                                ....
                            }
                        });

3-3异步下载更新进度条

/**
 * 主线程——>start
 * 点击按钮 |
 * 发起下载 |
 * 开启子线程下载 |
 * 下载过程中通知主线程 |——>主线程更新UI
 *
 */

注意事项:

一、读写文件的时候要获取权限

1.在AndroidManifest.xml中声明权限

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET"/>

2.android6.0之后要在动态申请权限

    //android6.0之后要动态获取权限
    private void checkPermission(Activity activity) {
        // Storage Permissions
        final int REQUEST_EXTERNAL_STORAGE = 1;
        String[] PERMISSIONS_STORAGE = {
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE};

        try {
            //检测是否有写的权限
            int permission = ActivityCompat.checkSelfPermission(DownLoadActivity.this,
                    "android.permission.WRITE_EXTERNAL_STORAGE");
            if (permission != PackageManager.PERMISSION_GRANTED) {
                // 没有写的权限,去申请写的权限,会弹出对话框
                ActivityCompat.requestPermissions(DownLoadActivity.this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

全局变量定义

    public static final int DOWNLOAD_MESSAGE_CODE = 1001;
    private static final int DOWNLOAD_MESSAGE_FAIL_CODE = 1000;
    public static final String appUrl = "http://download.sj.qq.com/upload/connAssitantDownload/upload/MobileAssistant_1.apk";
    private static Handler handler;
    private TextView textview;
    private int progress;
    public ProgressBar progressBar;

 二、主线程进行UI更新

        /**
         * 主线程进行UI更新
         * 进度条的更新
         */
        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case DOWNLOAD_MESSAGE_CODE://下载发过来的msg.what(消息编号1001)
                        progressBar.setProgress((Integer) msg.obj);
                        //textview.setText(msg.obj+"%");
                        break;
                    case DOWNLOAD_MESSAGE_FAIL_CODE://下载失败时消息编号为1000
                        Log.i("download", "fail");
                        Toast.makeText(DownLoadActivity.this,
                                "下载失败!", Toast.LENGTH_SHORT)
                                .show();
                        break;
                }
            }
        };

三、子线程进行耗时操作(下载、网络请求)

  findViewById(R.id.button_download).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                /**
                 * 子线程中进行下载
                 */
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        download(appUrl);

                    }
                }).start();//别忘了start()

            }
        });

四、下载的方法

    private void download(String appUrl) {
        try {
            URL url = new URL(appUrl);
            //打开url对象的链接,获得connection链接
            URLConnection urlConnection = url.openConnection();
            //获取文件的输入流(读取数据)
            InputStream inputStream = urlConnection.getInputStream();
            //获取文件的总长度
            int contentLength = urlConnection.getContentLength();
            //创建存储位置,获取sd卡的存储路径/storage/emulated/0/imooc/
            String downloadFolderName = Environment.getExternalStorageDirectory()
                    + File.separator + "imooc" + File.separator;
            //创建这个文件夹
            File file = new File(downloadFolderName);
            if (!file.exists()) {
                file.mkdirs();
            }
            //再上一个文件夹下又创建一个文件夹
            String fileName = downloadFolderName + "imooc.apk";
            File apkFile = new File(fileName);
            //如果已经有这个文件了,就删除重新下载过
            if (apkFile.exists()) {
                apkFile.delete();
            }
            //当前下载的长度处于文件总长度就是progress
            int downloadSize = 0;
            //建立一个字节数组类似缓存
            byte[] bytes = new byte[1024];

            int length = 0;
            //输出流将数据写入到这个文件里面
            OutputStream outputStream = new FileOutputStream(fileName);
            //从inputStream中读取数据,当没到文件末尾
            while ((length = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, length);
                downloadSize += length;//第一次下载1024,第二次又是1024,累加
                /**
                 * 更新UI
                 */
                Message message = Message.obtain();
                //将当前的进度传给主线程更新UI
                message.obj = downloadSize * 100 / contentLength;
                progress = downloadSize * 100 / contentLength;
                message.what = DOWNLOAD_MESSAGE_CODE;
                //1将消息发送给主线程
                handler.sendMessage(message);
                //2用runOnUiThread更新UI,也可以放到主线程里面进行更新
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        textview.setText(progress + "%");
                        //下载完成提示
                        if (progress == progressBar.getMax()) {
                            Toast.makeText(DownLoadActivity.this,
                                    "下载完成!", Toast.LENGTH_SHORT)
                                    .show();
                        }
                    }
                });
            }
            inputStream.close();
            outputStream.close();

            //下载失败也要发消息
        } catch (MalformedURLException e) {
            notifyDownloadFail();
            e.printStackTrace();
        } catch (IOException e) {
            notifyDownloadFail();
            e.printStackTrace();
        }

    }
    //下载失败也要将消息发给主线程处理
    private void notifyDownloadFail() {
        Message message = Message.obtain();
        message.what = DOWNLOAD_MESSAGE_FAIL_CODE;
        //将消息发送给主线程
        handler.sendMessage(message);
    }

效果:

在/storage/emulated/0/imooc/里下载好的imooc.apk

3-4倒计时实现

什么是内存泄漏?

/**内存泄漏:
 * Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,
 * 这个后台线程在任务执行完毕(例如图片下载完毕)之后,
 * 通过消息机制通知Handler,然后Handler把图片更新到界面
 * 然而,如果用户在网络请求过程中关闭了Activity
 * 正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完
 * 该线程该线程持有Handler的引用,这个Handler又持有Activity的引用,
 * 依然持有Activity的引用(TextView)countdowntime
 * 所以Activity关闭后,就导致该Activity无法被GC回收(即内存泄露)
 */

方法1:直接使用Handler来处理,缺点:容易造成内存泄漏

         
           Handler handler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                 super.handleMessage(msg);
                //循环发消息控制
                if(msg!=null&&msg.what==COUNDOWN_TIME_CODE){
                    int value=9;
                    countdowntime.setText(value--);
               //重写构造一个message,重新发回去(不能直接用msg,否则会闪退)
                        Message message = Message.obtain();
                        message.what = COUNDOWN_TIME_CODE;
                        message.arg1 = value;
                        sendMessageDelayed(message, 1000);
                }
            }
        };

     //创建一个静态的Handler,不会发生内存泄漏
        Handler handler = new Handler(this);
        Message message = Message.obtain();//从消息池里面拿消息
        message.what = COUNDOWN_TIME_CODE;//消息编号
        message.arg1 = MAX_COUNT;
        //第一次发生message将消息延迟1s发送
        handler.sendMessageDelayed(message, DELAY_MILLIS);

    }

方法2://创建一个静态的Handler,不会发生内存泄漏

  弱引用:

  • 可以通过弱引用拿到我们的Activity,再通过Acitivity拿到控件
  •  不会内存泄漏的原因
  •  因为这个MainActivity是弱引用的
  •  当我们持有它,用完之后就会被回收了
public class MainActivity extends AppCompatActivity {

    public static final int COUNDOWN_TIME_CODE = 1001;//倒计时handler code
    public static final int DELAY_MILLIS = 1000;//倒计时间隔
    public static final int MAX_COUNT = 10;//倒计时最大值
    private TextView countdowntime;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //得到控件
        countdowntime = findViewById(R.id.textview);

        //创建一个静态的Handler,不会发生内存泄漏
        CountdownTimeHandler handler = new CountdownTimeHandler(this);
        Message message = Message.obtain();//从消息池里面拿消息
        message.what = COUNDOWN_TIME_CODE;//消息编号
        message.arg1 = MAX_COUNT;
        //第一次发生message将消息延迟1s发送
        handler.sendMessageDelayed(message, DELAY_MILLIS);
    }

    //直接写一个静态的Handler
    public static class CountdownTimeHandler extends Handler {
        //弱引用,可以通过弱引用拿到我们的Activity,再通过Acitivity拿到控件
        final WeakReference<MainActivity> mWeakReference;

        //默认构造方法 command+N 在构造方法中初始化
        public CountdownTimeHandler(MainActivity activity) {
            /**不会内存泄漏的原因
             * 因为这个MainActivity是弱引用的
             * 当我们持有它,用完之后就会被回收了
             */
            //获取当前Activity的弱引用
            mWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //拿到弱引用的MainActivity
            MainActivity activity = mWeakReference.get();
            switch (msg.what) {
                case COUNDOWN_TIME_CODE:
                    int value = msg.arg1;
                    //拿到textview
                    activity.countdowntime.setText(String.valueOf(value--));

                    //循环发消息控制
                    if (value >= 0) {
                        //重写构造一个message,重新发回去(不能直接用msg,否则会闪退)
                        Message message = Message.obtain();
                        message.what = COUNDOWN_TIME_CODE;
                        message.arg1 = value;
                        sendMessageDelayed(message, 1000);
                    }


            }
        }


    }
}

结果:

3.5-打地鼠游戏的实现(改)

思路:第一次发送游戏开始消息,之后由自定义的静态Hanlder处理,然后由它循环发送消息编号一致的消息,它自己接受后处理

代码:DigLetAcitivity.java

import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.lang.ref.WeakReference;
import java.util.Random;

public class DigLetActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {

    private static final int RANDOM_NUMBER = 500;
    private final int START_TIME = 3;//开始倒计时
    private TextView resultTextView, start_timeTv;
    private ImageView diglettImageView;
    private Button start_button;
    //随机生成地鼠的位置
    public int[][] mPosition = new int[][]{
            {342, 180}, {432, 880}, {332, 110},
            {782, 140}, {466, 380}, {732, 900},
            {52, 180}, {562, 80}, {72, 80},
            {342, 180}, {432, 880}, {332, 110},
            {882, 50}, {652, 130}, {332, 110},
            {452, 90}, {132, 340}, {532, 670},
            {342, 100}, {132, 280}, {432, 610},
            {92, 60}, {832, 880}, {762, 90},
    };
    private int mTotalCount;//所有地鼠的数量
    private int mSuccessCount;//成功的数量
    //初始化handler
    private DiglettHandler mHandler = new DiglettHandler(this);
    //全局变量
    public static final int MAX_COUNT = 100;
    public static final int NEXT_CODE = 123;
    public static final int START_TIME_CODE = 1234;//倒计时编号
    private Button start_again;
    private static int startTime;
    private ImageView girlImageView;
    private Vibrator vibrator;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dig_let);
        setTitle("Kiss Game");
        initView();
        //点击图片手机震动
        vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);

    }

    private void initView() {
        start_timeTv = findViewById(R.id.start_textview);
        resultTextView = findViewById(R.id.text_view);
        diglettImageView = findViewById(R.id.image_view_pig);
        girlImageView = findViewById(R.id.image_view_girl);
        start_again = findViewById(R.id.button_again);
        start_button = findViewById(R.id.start_button);
        start_button.setOnClickListener(this);
        start_again.setOnClickListener(this);
        diglettImageView.setOnTouchListener(this);//图片点击事件
        girlImageView.setOnTouchListener(this);//图片失败点击事件
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start_button://开始按钮
                start();
                break;
            case R.id.button_again://重开按钮
                clear();
                break;
        }

    }

    //第一次发送消息
    private void start() {
        //发送消息hander.sendMessageDelayer
        //初始化
        startTime();//倒计时
        resultTextView.setText("Start~");
        start_button.setText("Playing..");
        start_button.setEnabled(false);//设置按钮不能再按了
        next(0);

    }

    //倒计时
    private void startTime() {
        Message message = Message.obtain();
        message.what = START_TIME_CODE;//消息编码
        message.arg2 = START_TIME;//倒计时的数
        //发消息
        mHandler.sendMessageDelayed(message, 1000);
    }

    //这只打完,下一只出来
    private void next(int delayTime) {
        //生成地鼠个数,随机选
        int position = new Random().nextInt(mPosition.length);

        Message message = Message.obtain();
        message.what = NEXT_CODE;//消息编码
        message.arg1 = position;//发随机地鼠的位置
        //发消息
        mHandler.sendMessageDelayed(message, delayTime);
        mTotalCount++;//每次执行一次next总数累加

    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (v.getId()) {
            case R.id.image_view_pig:
                v.setVisibility(View.GONE);//当地鼠被点击后,设置为不可见
                mSuccessCount++;//成功点击的数量
                break;
            case R.id.image_view_girl:
                vibrator.vibrate(100);//手机震动100毫秒
                v.setVisibility(View.GONE);//当地鼠被点击后,设置为不可见
                mSuccessCount--;//点击失败的数量

        }

        resultTextView.setText(" Kiss: " + mSuccessCount + " Times");

        return false;
    }

    //新建一个静态Handler,防止内存泄漏
    public static class DiglettHandler extends Handler {
        //弱引用
        public final WeakReference<DigLetActivity> mWeakReference;

        public DiglettHandler(DigLetActivity activity) {
            mWeakReference = new WeakReference<>(activity);
        }

        //接受到消息进行处理
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //拿到弱引用的Activity
            DigLetActivity activity = mWeakReference.get();

            switch (msg.what) {
                case START_TIME_CODE:
                    //拿到倒计时时间
                    startTime = msg.arg2;
                    if (startTime > 0) {
                        //显示出来
                        activity.start_timeTv.setText(String.valueOf(startTime--));
                        //重写构造一个message,重新发回去(不能直接用msg,否则会闪退)
                        Message message = Message.obtain();
                        message.what = START_TIME_CODE;
                        message.arg2 = startTime;
                        sendMessageDelayed(message, 1000);
                    } else {
                        activity.start_timeTv.setText(String.valueOf(startTime--));
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    Thread.sleep(1000);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                        });
                        activity.start_timeTv.setVisibility(View.GONE);
                    }

                case NEXT_CODE:
                    //停止条件
                    if (activity.mTotalCount > MAX_COUNT) {
                        activity.clear();
                        Toast.makeText(activity, "Game Over", Toast.LENGTH_SHORT).show();
                        return;
                    }
                    if (startTime < 0) {
                        //拿到坐标
                        int position = msg.arg1;
                        //设置图片的X坐标(二维数组)0是第一个:X。1是第二个:Y
                        activity.diglettImageView.setX(activity.mPosition[position][0]);
                        activity.diglettImageView.setY(activity.mPosition[position][1]);
                        //让图片可见
                        activity.diglettImageView.setVisibility(View.VISIBLE);
                        //设置图片的X坐标(二维数组)0是第一个:X。1是第二个:Y
                        activity.girlImageView.setX(activity.mPosition[position][1]);
                        activity.girlImageView.setY(activity.mPosition[position][0]);
                        //让图片可见
                        activity.girlImageView.setVisibility(View.VISIBLE);
                        //生成随机时间毫秒为单位
                        int randomTime = new Random().nextInt(RANDOM_NUMBER) + RANDOM_NUMBER;
                        activity.next(randomTime);//将时间传给下一个//
                    }
                    break;
            }

        }
    }

    //清空
    public void clear() {
        mTotalCount = 0;
        mSuccessCount = 0;
        diglettImageView.setVisibility(View.GONE);
        startTime = 3;
        start_timeTv.setText("");
        start_timeTv.setVisibility(View.VISIBLE);
        resultTextView.setText("");
        start_button.setText("Start");
        start_button.setEnabled(true);
    }

}

鼠标跟踪图片效果: LockScreenView.java

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;

public class LockScreenView extends ImageView {
    public float currentX = 40;
    public float currentY = 50;
    private Bitmap bmp;

    public LockScreenView(Context context) {
        super(context);
        init();
    }

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

    public LockScreenView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    //初始化你需要显示的光标样式
    private void init() {

        if (bmp == null) {
            bmp = BitmapFactory.decodeResource(getResources(), R.drawable.kissyou);
        }
    }

    private boolean isClickView = false;//标识是否是人为点击,是则为true

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (isClickView == true && bmp != null) {
            //创建画笔
            Paint p = new Paint();
            canvas.drawBitmap(bmp, currentX - (bmp.getWidth() / 2), currentY - (bmp.getHeight() / 2), p);
            isClickView = false;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //当前组件的currentX、currentY两个属性
        this.currentX = event.getX();
        this.currentY = event.getY();
        isClickView = true;

        if (event.getAction() == MotionEvent.ACTION_UP && bmp != null) {
            this.currentX = -bmp.getWidth();
            this.currentY = -bmp.getHeight();
            isClickView = false;
        }
        //通知改组件重绘
        this.invalidate();
        //返回true表明处理方法已经处理该事件
        return false;
    }
}

点错手机震动效果:

1、AndroidManifest.xml获取权限

    <uses-permission android:name="android.permission.VIBRATE"/>

2、 创建对象

    private Vibrator vibrator;
 //点击图片手机震动
        vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);

点击事件中添加震动时间 

                vibrator.vibrate(100);//手机震动100毫秒

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值