Android基础杂记



    • 添加LitePal依赖时出现的问题和解决方法

      1. 使用implementation命令失败(内层build.gradle)

        dependencies {
            ...
            implementation 'org.litepal.android:java:3.0.0'
        }
        

        可能是因为build tools版本过低

        修改外层build.gradle

        buildscript {
            repositories {
                jcenter()
                #添加google
                google()
            }
            dependencies {
            	#修改版本
                classpath 'com.android.tools.build:gradle:3.0.0'
            }
        }
        

        修改gradle-wrapper.properties
        ~~~js
        distributionUrl=https://services.gradle.org/distributions/gradle-4.1-all.zip
        ~~~
        > 更多详情可浏览 https://stackoverflow.com/questions/45838173/gradle-dsl-method-not-found-implementation/45838308

      2. 报错Error:This Gradle plugin requires Studio 3.0 minimum

        在gradle.properties中添加

        android.injected.build.model.only.versioned=3
        android.injected.testOnly=false
        
      3. 创建项目时出错(具体忘记了)

        修改外层build.gradle

        allprojects {
            repositories {
                jcenter()
                #添加以下这一句
                maven { url 'https://maven.google.com'}
            }
        }
        
    • Android通知时的问题

      1. Android 8.0以上通知方法如下

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            NotificationManager manager =  (NotificationManager)getSystemService(NOTIFICATION_SERVICE) ;
            String notificationId = "MychannelId";
            String notificationName = "MychannelName";
            #设置通道
            NotificationChannel channel = new NotificationChannel(notificationId, notificationName, NotificationManager.IMPORTANCE_LOW);
            #创建通道
            manager.createNotificationChannel(channel);
            #通知设置
            Notification notification = new NotificationCompat.Builder(view.getContext(), notificationId)
                #设置通道id
                .setContentTitle("This is a title")
                .setContentText("text text text text text")
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), 		R.mipmap.ic_launcher))
                .setContentIntent(pi)
                .setAutoCancel(true)
                .build();
            manager.notify(1, notification);
        }
        
    1. 否则不需要通道

      Notification notification = new NotificationCompat.Builder(view.getContext(), "default")
          .setContentTitle("This is a title")
          .setContentText("text text text text text")
          .setWhen(System.currentTimeMillis())
          .setSmallIcon(R.mipmap.ic_launcher)
          .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
          .setContentIntent(pi)
          .setAutoCancel(true)
          .build();
      manager.notify(1, notification);
      


    • 下载案例分析

      • AsyncTask的使用(DownloadTask)

        img

      使用AsyncTask封装的多线程以及异步消息处理机制来实现下载及UI更新的操作

      使用publishProgress() 手动对onProgressUpdate() 进行调用更新UI

      onProgressUpdate()中进行UI的修改操作时,需要使用从调用者处获取的UI控件,可在构造函数中获取

      doInBackground() 的返回值会在onPostExecute() 进行处理,故定义状态变量

      public static final int TYPE_SUCCESS = 0 ;
      public static final int TYPE_FAILED = 1 ;
      public static final int TYPE_PAUSED = 2 ;
      public static final int TYPE_CANCELED = 3 ;
      

      用以记录和处理下载的结果

      • DownloadListener

        public interface DownloadListener {
            //    用于实现下载回调处理方法
            void onProgress(int progress) ;
            void onSuccess() ;
            void onFailed() ;
            void onPaused() ;
            void onCanceled() ;
        }
        

      编写DownloadListener接口并在DownloadService中实现作为参数传递给下载实现类DownloadTask

      DownloadTask调用DownloadListener提供的方法用以将执行结果更新到UI中

      • DownloadService

      DownloadService的主要功能是为DownloadTask开启一个后台的服务,便于在后台执行耗时操作

      使用Service并不会开启线程,耗时操作需要手动开启线程,故可使用上面的AsyncTask开启

      也可使用IntentService,可自动开启关闭线程

      在DownloadService中实现了不同状态UI更新的方法,供DownloadTask回调使用

      另外DownloadService与活动绑定,便于用户操作控制服务的状态

      • DownloadActivity

      作为主界面活动,通过点击不同按钮,调用binder提供的控制方法,控制与其绑定的服务状态

      申请程序运行所需要的权限



    • 申请权限

      1. 在AndroidManifest.xml中声明

        <uses-permission android:name="android.permission.INTERNET"/>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
        
      2. 通常格式

        if(ContextCompat.checkSelfPermission(MainActivity.this, 					Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(MainActivity.this,
            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        }
        
      3. 对用户选择结果进行处理的回调函数

        @Override
            public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
                #根据requestCode对不同的权限申请结果进行处理
                switch(requestCode){
                    case 1:
                        if(grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED){
                            Toast.makeText(this, "You denied the permission!", Toast.LENGTH_SHORT).show();
                            finish();
                        }
                        break;
                    default:
                        break;
                }
            }
        


    • ListView布局控件

      1. 最简单的使用方法,用来显示一组简单的字符串数据

        #声明适配器,构造函数参数为 <上下文、子项布局、数据集合>
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(ListActivity.this,
                        android.R.layout.simple_list_item_1, data) ;
        #ListView对象
        ListView listView = (ListView)findViewById(R.id.listview) ;
        #设置适配器
        listView.setAdapter(adapter);
        
      2. 自定义子项布局和数据集合对象

        • 定义一个子项布局文件item_layout.xml

        • 编写自定义适配器

          class MyAdapter extends ArrayAdapter<String>{
              private int resourceId ;
              
              #构造函数保存resourceId以备使用
              public MyAdapter(Context context, int resource, String[] objects) {
                  super(context, resource, objects);
                  this.resourceId = resource ;
          }
          
        • 在MyAdapter中编写ViewHolder

          class ViewHolder{
              TextView textView ;
              Button btn ;
          }
          
        • 覆写MyAdapter中的getView()方法以供子项加载时调用

          1. 获取子项元素对象

            String str = getItem(position) ;
            
          2. 获取子项布局组件,给子项赋值

            #定义子项View和ViewHolder
            View view ;
            ViewHolder holder ;
            #判断是否存在缓存
            if(convertView == null){
                //                如果之前没有缓存,则获取UI和控件的信息,保存到view当中
                view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false) ;
                holder = new ViewHolder() ;
                #获取子项布局组件
                holder.textView = (TextView)view.findViewById(R.id.item_name) ;
                holder.btn = (Button)view.findViewById(R.id.item_btn) ;
                view.setTag(holder);
            }else{
                //                如果存在缓存则直接读取view
                view = convertView ;
                holder = (ViewHolder) view.getTag() ;
            }
            #设置子项信息
            holder.textView.setText(str);
            });
            
    • ListView和RecyclerView的不同

      1. ListView只要重写getView()一个函数,在其中完成对子项布局的加载,子项数据的缓存,子项显示数据的设置

        RecyclerView 在 onCreateViewHolder() 中加载子项布局,通过构造函数把View对象传递给ViewHolder,函数返回一个ViewHolder对象

        在ViewHolder的构造函数中获取子项布局的对象

        在onBindViewHolder() 中对子项显示数据进行设置

      2. ListView需要手动把ViewHolder对象保存在view中,需要的时候手动提取

        RecyclerView封装了ViewHolder在需要的函数都会作为参数提供



  • Android屏幕亮度调节 + 一个好用的计时器类


    • 调节当前窗口亮度

      public void setScreenLight(int screenLight){
          Window localWindow = getWindow();
          WindowManager.LayoutParams localLayoutParams = localWindow.getAttributes();
          float f = screenLight / 255.0F;
          // 保存的系统设置亮度值为百分制的值
          localLayoutParams.screenBrightness = f;
          localWindow.setAttributes(localLayoutParams);
      }
      public int getScreenLight(){
          return (int)(getWindow().getAttributes().screenBrightness * 255.0F) ;
      }
      
    • 修改系统亮度设置

      1. 声明系统设置权限

        <uses-permission android:name="android.permission.WRITE_SETTINGS" />
        
      2. 动态申请系统设置权限

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //如果当前平台版本大于23平台
            if (!Settings.System.canWrite(this)) {
                //如果没有修改系统的权限这请求修改系统的权限
                Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
                intent.setData(Uri.parse("package:" + getPackageName()));
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivityForResult(intent, 0);
            }
        }
        
      3. 获取和修改系统屏幕亮度模式

        //SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1 为自动调节屏幕亮度
        //SCREEN_BRIGHTNESS_MODE_MANUAL = 0  为手动调节屏幕亮度 
        Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE);
        
        Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); //设置为手动调节
        
      4. 获取和修改系统屏幕亮度

        //paramInt ∈ (0, 255)
        Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS);
        Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, paramInt);
        
    • 计时器类CountDownTimer

      CountDownTimer timer ;
      //第一个参数为倒计时总时间,第二个参数为每次递减时间间隔,此处为计时1分钟
      timer = new CountDownTimer(60000, 1000) {
          @Override
          public void onTick(long l) {
      
          }
      
          @Override
          public void onFinish() {
              
          }
      } ;
      timer.start() ; //开启计时器
      timer.cancel() ; //关闭计时器
      


  • Android存储路径

    1. 内部存储

    • getFilesDir()

      应用文件存储目录 /data/data/包名/files

    • openFileOutput(fileName, Context.MODE_PRIVATE)

      打开内部存储文件输出流

      openFileInput()同理

    2. 外部存储

    • Environment.getExternalStorageDirectory()

      外部存储根目录 /storage/emulated/0

    • getExternalFilesDir("")

      外部存储文件夹 /storage/emulated/0/Android/data/包名/files



  • Android 数据绑定通信

    一、观察者模式

    1. 被观察者

    定义一个类继承自Observable父类

    public class BookObservable extends Observable {
        public void postNewBook(String name){
            // 通过这两个方法来实现消息的更新和对观察者的通知
            setChanged();
            notifyObservers(name);
        }
    }
    

    2. 观察者

    定义一个类继承自Observer接口并实现update方法

    public class UserObserver implements Observer {
        private static final String TAG = UserObserver.class.getSimpleName() ;
        private String name ;
    
        public UserObserver(String name){
            this.name = name ;
        }
    
        // 当被观察者更新信息时该方法会被自动调用
        @Override
        public void update(Observable o, Object arg) {
            Log.d(TAG, this.name + " get new book => " + arg);
        }
    }
    

    3. 绑定观察者

    public void observeTest() {
        // 新建观察者与被观察者
        BookObservable bookObservable = new BookObservable();
        UserObserver user1 = new UserObserver("A");
        UserObserver user2 = new UserObserver("B");
        UserObserver user3 = new UserObserver("C");
    
        // 为被观察者添加观察者
        bookObservable.addObserver(user1);
        bookObservable.addObserver(user2);
        bookObservable.addObserver(user3);
    
        // 被观察者更新内容
        bookObservable.postNewBook("kotlin");
    }
    

    二 、ViewModel 与 MutableLiveData

    1. ViewModel 类

    实现一个类继承自ViewModel父类

    并提供有MutableLiveData

    public class TestViewModel extends ViewModel {
        private MutableLiveData<String> name = new MutableLiveData<>() ;
    
        public MutableLiveData<String> getNameLiveData(){
            return this.name ;
        }
    
        public void setName(String name){
            this.name.setValue(name);
        }
    }
    

    2. 绑定ViewModel

    public void liveDataTest() {
        // 使用ViewModelProvider初始化ViewModel类
        TestViewModel testViewModel = new ViewModelProvider(this).get(TestViewModel.class);
        // 为数据添加观察者,实时监测数据变化并更新ui
        testViewModel.getNameLiveData().observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.d(TAG, "*** name change => " + s);
            }
        });
        // 当数据改变时上述的观察者onChanged方法会被自动调用
        testViewModel.getNameLiveData().setValue("kotlin");
    }
    


  • Android 自定义注解基础

    一、 运行时注解

    1. 声明注解

    以一个自动实现 findViewById 功能的注解为例

    // 声明注解的目标,比如说类属性
    @Target(ElementType.FIELD)
    // 声明注解的生存时间,比如直到运行时
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ViewById {
        // 注解可接收的参数
        int value() default -1 ;
    }
    

    2. 处理注解

    public void initAnnotation(){
        // 获取当前类的所有属性
        Field[] fields = this.getClass().getDeclaredFields() ;
        for(Field field : fields){
            // 设置属性可改变
            field.setAccessible(true);
            // 获取属性的注解
            ViewById viewById = field.getAnnotation(ViewById.class) ;
            if (viewById != null){
                try {
                    // 设置属性的值为findViewById的结果
                    field.set(this, findViewById(viewById.value()));
                    Log.d(TAG, "initAnnotation: " + field.getName());
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
    

    二、 编译时注解

    1. 声明注解

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.CLASS)
    public @interface LogTime {
    }
    

    2. 注解处理器

    新建一个项目module来编写注解处理器

    在build.gradle中导入必要的依赖

    apply plugin: 'java-library'
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
    
        // 导入接口定义的module
        implementation project(":annotationtest")
    
        // 用于自动注册注解处理器的插件
        // 二者缺一不可
        implementation 'com.google.auto.service:auto-service:1.0-rc6'
        annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
    
        // 用于方便的生成一个新的java文件
        implementation 'com.squareup:javapoet:1.12.1'
    }
    
    sourceCompatibility = "8"
    targetCompatibility = "8"
    
    

    定义注解处理器继承自 AbstractProcessor 父类

    // 使用插件的注解自动注册注解
    @AutoService(Processor.class)
    public class LogTimeProcessor extends AbstractProcessor {
        private Types typeUtils ;
        private Elements elementUtils ;
        private Filer filer ;
        private Messager messager ;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            // 可能用到的工具类
            this.typeUtils = processingEnv.getTypeUtils() ;
            this.filer = processingEnv.getFiler() ;
            this.messager = processingEnv.getMessager() ;
            this.elementUtils = processingEnv.getElementUtils() ;
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            
            // ...处理注解
            
            return true ;
        }
    
        // 定义版本
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latest() ;
        }
    
        // 定义处理的注解类型,该方法一定要覆写,并且需要返回包含处理类型的集合列表
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            HashSet<String> hashSet = new LinkedHashSet<>() ;
            hashSet.add(LogTime.class.getCanonicalName()) ;
            return hashSet ;
        }
    }
    

    3. javapoet 基本写法

    // 创建一个方法
    MethodSpec logTimeMethod = MethodSpec.methodBuilder("logTime") //方法名
            .addModifiers(Modifier.PUBLIC) // 修饰符
            .addStatement("T.out.println(T.out.println(S)", System.class, "Hello") // 语句
        	.build() ; // 生成方法
    
    // 创建一个类
    TypeSpec typeSpec = TypeSpec.classBuilder(className) // 传入类的完整包名加类名
                        .addModifiers(Modifier.PUBLIC) //修饰符
                        .addMethod(logTimeMethod) // 添加类方法
                        .build() ; // 生成类
    
    // 创建java文件
    JavaFile javaFile = JavaFile.builder(packageName, typeSpec).build() ;
    try {
        // 通过filer工具写入文件
        javaFile.writeTo(filer);
    } catch (IOException e) {
        e.printStackTrace();
    }
    


  • 关于DataBinding与MVVM框架

    一、DataBinding

    DataBinding 是Google提供的一种数据绑定支持库

    可以将一个视图的组件数据与一个类对象的数据一对一绑定

    并支持双向绑定

    当类对象数据更新时,视图也可以自动实时更新

    当视图组件比如输入框的数据更新时,类对象数据也可以实时更新

    1. 开启DataBinding

    在 app 的 build.gradle 里 android 配置下添加如下

    dataBinding{
        enabled true
    }
    

    2. 修改视图文件

    直接在视图文件最外层 Layout 标签上使用快捷键 alt + enter 将其转换为 DataBinding 模式

    修改 data 标签设置

    <data>
        <variable
           	可以给该视图对应的对象引用取一个名字,便于调用该对象的属性值
        	name="myUser"
                  
            该视图对应的类对象类型详细路径
            type="com.example.mvvmdemo.User" />
    </data>
    

    3. 绑定视图与对象

    //该视图的文件名称为 main_layout
    //则系统会自动生成一个名为 MainLayoutBinding 的对象
    MainLayoutBinding binding = DataBindingUtil.setContentView(this, R.layout.main_layout) ;
    //初始化一个我们在视图中指定的类型的对象
    User user = new User() ;
    //将对象与视图绑定
    //这里的setMyUser方法是根据我们在视图中给对象取的别名自动生成的
    binding.setMyUser(user) ;
    

    4. 绑定对象属性与视图组件值

    假定我们在视图中有一个 TextView

    //设置它的值为user对象的name属性
    android:text="@{user.name}"
    

    修改User类,继承BaseObservable类

    class User extends BaseObservable{
        //提供对应的name属性
    	private String name ;
        //提供getter方法并添加注解
        @Bindable
        public String getName() {
            return name;
        }
        //提供setter方法并在修改值时实时更新视图
        public void setName(String name) {
            this.name = name;
            notifyPropertyChanged(BR.name);
        }
    }
    

    5. 双向绑定

    以上只能保证单向绑定,即修改数据实时更新视图

    若要实现视图更新时,数据也能实时更新,需要如下设置

    //在@之后添加一个=号
    android:text="@={user.name}"
    

    6. 点击方法绑定

    android:onClick="@{user.submit}"
    
    //需要注意的是绑定的点击方法必须传递一个View参数
    public void submit(View view){
        Log.d(TAG, "submit.") ;
    }
    

    二、MVVM框架

    MVVM 具体为 Model + View + ViewModel

    我们可以将 ViewModel 与 View 进行绑定

    所有的业务逻辑在 ViewModel 中进行处理,并且视图会实时更新

    所有的数据在 Model 中进行处理,并调用 ViewModel 提供的回调接口修改视图



  • android事件分发

    1. 点击事件(MotionEvent)

    • MotionEvent.ACTION_DOWN
    • MotionEvent.ACTION_UP
    • MotionEvent.ACTION_MOVE
    • MotionEvent.ACTION_CANCEL (move中因非人为事件取消)

    2. 分发对象

    • Activity
    • ViewGroup
    • View

    3. 分发函数

    • dispatchTouchEvent() 处理事件传递
    • onTouchEvent() 处理事件点击
    • onInterceptTouchEvent() (只有ViewGroup中有,处理事件拦截)

    4. Activity源码

    • Activity dispatchTouchEvent()
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            //用于得知用户正在与设备交互
            //为空方法,可覆写,只在Down时调用一次
            //当Activity在栈顶时,点击home、back、menu都会触发
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            //返回true则事件停止传递
            return true;
        }
        //处理Activity的事件点击
        return onTouchEvent(ev);
    }
    
    • Activity getWindow().superDispatchTouchEvent()

    getWindow() -> Window类 -> PhoneWindow类(唯一实现类)

    DecorView -> FrameLayout -> ViewGroup

    //PhoneWindow对象的方法
    public boolean superDispatchTouchEvent(MotionEvent event) {
    	return mDecor.superDispatchTouchEvent(event);
    }
    //DecorView对象的方法
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
    
    • Activity onTouchEvent()
    public boolean onTouchEvent(MotionEvent event) {
    	if (mWindow.shouldCloseOnTouch(this, event)) {
    		finish();
    		return true;
    	}
    	return false;
    }
    
    • Activity mWindow.shouldCloseOnTouch()

    Window类的方法

    public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
        final boolean isOutside =
            event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
            || event.getAction() == MotionEvent.ACTION_OUTSIDE;
        if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
            return true;
        }
        return false;
    }
    private boolean isOutOfBounds(Context context, MotionEvent event) {
        final int x = (int) event.getX();
        final int y = (int) event.getY();
        final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
        final View decorView = getDecorView();
        return (x < -slop) || (y < -slop)
            || (x > (decorView.getWidth()+slop))
            || (y > (decorView.getHeight()+slop));
    }
    

    4. ViewGroup源码

    • ViewGroup dispatchTouchEvent()
    public boolean dispatchTouchEvent(MotionEvent ev) {
        
        //...
    
        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    
        if (action == MotionEvent.ACTION_DOWN) {
            
            //...
            
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {
                // reset this event's action (just to protect ourselves)
                ev.setAction(MotionEvent.ACTION_DOWN);
                // We know we want to dispatch the event down, find a child
                // who can handle it, start with the front-most child.
                final int scrolledXInt = (int) scrolledXFloat;
                final int scrolledYInt = (int) scrolledYFloat;
                final View[] children = mChildren;
                final int count = mChildrenCount;
    
                for (int i = count - 1; i >= 0; i--) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                        || child.getAnimation() != null) {
                        child.getHitRect(frame);
                        if (frame.contains(scrolledXInt, scrolledYInt)) {
                            // offset the event to the view's coordinate system
                            final float xc = scrolledXFloat - child.mLeft;
                            final float yc = scrolledYFloat - child.mTop;
                            ev.setLocation(xc, yc);
                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
                            if (child.dispatchTouchEvent(ev))  {
                                // Event handled, we have a target now.
                                mMotionTarget = child;
                                return true;
                            }
                            // The event didn't get handled, try the next view.
                            // Don't reset the event's location, it's not
                            // necessary here.
                        }
                    }
                }
            }
        }
    
        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
            (action == MotionEvent.ACTION_CANCEL);
    
        if (isUpOrCancel) {
            // Note, we've already copied the previous state to our local
            // variable, so this takes effect on the next event
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }
    
        // The event wasn't an ACTION_DOWN, dispatch it to our target if
        // we have one.
        final View target = mMotionTarget;
        if (target == null) {
            // We don't have a target, this means we're handling the
            // event as a regular view.
            ev.setLocation(xf, yf);
            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
                ev.setAction(MotionEvent.ACTION_CANCEL);
                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            }
            return super.dispatchTouchEvent(ev);
        }
    
        //...
    
        return target.dispatchTouchEvent(ev);
    }
    

    5. View源码

    • View dispatchTouchEvent()
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (!onFilterTouchEventForSecurity(event)) {
            return false;
        }
    
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
            mOnTouchListener.onTouch(this, event)) {
            return true;
        }
        return onTouchEvent(event);
    }
    
    • View onTouchEvent()
    public boolean onTouchEvent(MotionEvent event) {
        
        //...
    
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
             (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                    if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }
    
                        if (!mHasPerformedLongPress) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();
    
                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
    
                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }
    
                        if (prepressed) {
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                            postDelayed(mUnsetPressedState,
                                        ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    break;
    
                case MotionEvent.ACTION_DOWN:
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPrivateFlags |= PREPRESSED;
                    mHasPerformedLongPress = false;
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    break;
    
                case MotionEvent.ACTION_CANCEL:
                    mPrivateFlags &= ~PRESSED;
                    refreshDrawableState();
                    removeTapCallback();
                    break;
    
                case MotionEvent.ACTION_MOVE:
                    final int x = (int) event.getX();
                    final int y = (int) event.getY();
    
                    // Be lenient about moving outside of buttons
                    int slop = mTouchSlop;
                    if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                        (y < 0 - slop) || (y >= getHeight() + slop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();
    
                            // Need to switch from pressed to not pressed
                            mPrivateFlags &= ~PRESSED;
                            refreshDrawableState();
                        }
                    }
                    break;
            }
            return true;
        }
        return false;
    }
    
    • View performClick()
    public boolean performClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    
        if (mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            mOnClickListener.onClick(this);
            return true;
        }
    
        return false;
    }
    

    6. 总结

    0_1599383579564_AndroidTouch.jpg


 

Copyright © 2018 bbs.dian.org.cn All rights reserved.

Looks like your connection to Dian was lost, please wait while we try to reconnect.