Android基础杂记
-
-
Android简介
-
系统架构
- Linux内核层
- 系统运行层
- 应用框架层
- 应用层
-
四大组件
- 活动(Activity)
- 服务(Service)
- 广播接收器(Broadcast Receiver)
- 内容提供器(Content Provider)
-
活动的生存周期
- onCreate 第一次被创建时调用
- onStart 由不可见变为可见时调用
- onResume 准备好和用户进行交互时调用,此时活动处于栈顶
- onPause 活动准备去启动或恢复另一个活动时调用
- onStop 活动完全不可见时调用
- onDestroy 活动销毁时调用
- onRestart 活动由停止变为运行状态时调用
完整生存期 onCreate() 到 onDestroy()
可见生存期 onStart() 到 onStop()
前台生存期 onResume() 到 onPause()
-
启动模式
- standard 每次点击都会创建一个新的活动放入返回栈
- singleTop 如果栈顶活动是所需要的活动则不会创建新的活动
- singleTask 检查栈中所有活动,若存在所需活动直接使用,并将该活动以上的活动全部出栈
- singleInstance 每个活动拥有一个单独的返回栈
-
-
基本控件
-
TextView
<TextView android:id="@+id/text_view" android:text="this is a text" android:gravity="center" android:textSize="24sp" android:textColor="00ff00" android:layout_width="match_parent" android:layout_height="wrap_content" />
-
Button
<Button android:id="@+id/btn" android:text="Click Me" android:textAllCaps="false" android:layout_width="match_parent" android:layout_height="wrap_content" />
-
EditText
<EditText android:id="@+id/edit_text" android:hint="input here" android:maxLines="2" android:layout_width="match_parent" android:layout_height="wrap_content" />
-
ImageView
<ImageView android:id="@+id/image_view" android:src="@drawable/img_1" android:layout_width="wrap_content" android:layout_height="wrap_content" />
-
ProgressBar
<ProgressBar android:id="@+id/progress_bar" android:max="100" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" />
-
AlertDialog/ProgressDialog
-
-
基本布局
-
线性布局 LinearLayout
<Button android:layout_gravity="top" android:layout_gravity="center_vertical" android:layout_gravity="bottom" /> # 根据权重分配屏幕比例 <Button android:layout_width="0dp" android:layout_weight="1"/>
-
相对布局 RelativeLayout
-
帧布局 FrameLayout
-
百分比布局 PercentFrameLayout/PercentRelativeLayout
-
约束布局ConstraintLayout
更多使用方法可见链接https://www.jianshu.com/p/17ec9bd6ca8a
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"> <!--四边都设置对齐可以使组件居中显示--> <Button android:id="@+id/btn_5" android:text="button5" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> <!--btn_6左对齐btn_5右, btn_6下对齐btn_5上--> <Button android:id="@+id/btn_6" android:text="button6" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintLeft_toRightOf="@id/btn_5" app:layout_constraintBottom_toTopOf="@id/btn_5"/> <!--以btn_5为圆心,半径为300dp,顺时针转30度--> <Button android:id="@+id/btn_7" android:text="button7" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintCircle="@id/btn_5" app:layout_constraintCircleAngle="30" app:layout_constraintCircleRadius="300dp"/> </android.support.constraint.ConstraintLayout>
-
-
-
-
滚动控件RecyclerView
-
在app/builde.gradle中添加依赖
dependencies{ #或其它版本 compile 'com.android.support.recyclerview-v7:24.2.1' }
-
class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>
编写适配器继承关系如上
在构造函数中传入要显示的数据集合list
-
构建ViewHolder自定义保存滚动项的数据类型
static class ViewHolder extends RecyclerView.ViewHolder { TextView textview ; public void ViewHolder(View view){ super(view) ; textview = (TextView)findViewById(R.id.text_view) ; } }
-
设置滚动项的页面样式和点击事件
@Override public ViewHolder onCreateViewHolder(ViewGroup parent, int vieweType){ View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.view_layout, parent, false) ; ViewHolder holder = new ViewHolder(view) ; #设置点击监听事件 holder.textview.setOnclickListenr(new View.OnClickListener(){ @Override public void onClick(View v){ ... } }) ; return holder ; }
-
设置滚动项显示数据 (每当滚动项被滚动进入页面显示时会被执行)
@Override public void onBindViewHolder(ViewHolder holder, int position){ String data = list.get(position) ; holder.textview.setText(data) ; }
-
获取滚动项数量
@Override public int getItemCount(){ return list.size() ; }
-
调用
#RecyclerView控件 RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler_view) ; //直接通过改变不同的布局管理器可以实现不同的布局效果 #声明布局管理器 //线性布局 LinearLayoutManager layoutManager = new LinearLayoutManager(this) ; //瀑布流布局 StaggeredGridLayoutManager sManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL) ; //网格布局 GridLayoutManager gManager = new GridLayoutManager(this, 2) ; #设置布局管理器 recyclerView.setLayoutManager(layoutManager); #声明适配器 MyAdapter myAdapter = new MyAdapter(data) ; #设置适配器 recyclerView.setAdapter(myAdapter);
-
-
广播
-
广播接收函数
class NetworkChangeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { ... } }
-
动态注册广播
IntentFilter intentfilter = new IntentFilter() ; NetworkChangeReceiver ncReceiver = new NetworkChangeReceiver() ; #设置广播监听的值 intentfilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); #注册广播 registerReceiver(ncReceiver, intentfilter) ;
-
发送广播
Intent intent = new Intent("android.net.conn.CONNECTIVITY_CHANGE") ; sendBroadcast(intent);
-
静态注册广播
<receiver android:name=".MyReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> </intent-filter> </receiver>
-
-
-
-
添加LitePal依赖时出现的问题和解决方法
-
使用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 -
报错Error:This Gradle plugin requires Studio 3.0 minimum
在gradle.properties中添加
android.injected.build.model.only.versioned=3 android.injected.testOnly=false
-
创建项目时出错(具体忘记了)
修改外层build.gradle
allprojects { repositories { jcenter() #添加以下这一句 maven { url 'https://maven.google.com'} } }
-
-
Android通知时的问题
-
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); }
-
-
否则不需要通道
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)
使用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提供的控制方法,控制与其绑定的服务状态
申请程序运行所需要的权限
-
-
-
-
申请权限
-
在AndroidManifest.xml中声明
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
-
通常格式
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); }
-
对用户选择结果进行处理的回调函数
@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布局控件
-
最简单的使用方法,用来显示一组简单的字符串数据
#声明适配器,构造函数参数为 <上下文、子项布局、数据集合> 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);
-
自定义子项布局和数据集合对象
-
定义一个子项布局文件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()方法以供子项加载时调用
-
获取子项元素对象
String str = getItem(position) ;
-
获取子项布局组件,给子项赋值
#定义子项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的不同
-
ListView只要重写getView()一个函数,在其中完成对子项布局的加载,子项数据的缓存,子项显示数据的设置
RecyclerView 在 onCreateViewHolder() 中加载子项布局,通过构造函数把View对象传递给ViewHolder,函数返回一个ViewHolder对象
在ViewHolder的构造函数中获取子项布局的对象
在onBindViewHolder() 中对子项显示数据进行设置
-
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) ; }
-
修改系统亮度设置
-
声明系统设置权限
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
-
动态申请系统设置权限
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); } }
-
获取和修改系统屏幕亮度模式
//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); //设置为手动调节
-
获取和修改系统屏幕亮度
//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("
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. 总结