安卓开发笔记 开发必做 distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-7.3.3-bin.zip
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 buildscript { repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' allowInsecureProtocol = true } maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' allowInsecureProtocol = true } maven { url 'http://maven.aliyun.com/nexus/content/repositories/google' allowInsecureProtocol = true } maven { url 'http://maven.aliyun.com/nexus/content/repositories/gradle-plugin' allowInsecureProtocol = true } google() jcenter() } } allprojects { repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' allowInsecureProtocol = true } maven { url 'http://maven.aliyun.com/nexus/content/repositories/google' allowInsecureProtocol = true } maven { url 'http://maven.aliyun.com/nexus/content/repositories/gradle-plugin' allowInsecureProtocol = true } google() jcenter() } }
kotlin为
maven{ setUrl("http://maven.aliyun.com/nexus/content/groups/public/")}
crtl + p 提示
ctrl +win+alt+L格式化整理代码
测试安装不了
1 android.injected.testOnly=false
连接手机
先在电脑cmd
窗口adb tcpip 5555
,再把手机置静态IP连接热点,之后adb connect 192.168.137.66:5555
1、先确认Android设备开启开发者模式,并且开启USB调试;
2、确认Android设备和电脑处于同一局域网;
如果上述都确认还是出现 “由于目标计算机积极拒绝,无法连接。 (10061)” 这个问题,那就极有可能是端口被占用了:
(1)使用如下adb命令可以查看端口使用情况:
1 netstat -ano | findstr 5037
如果出现以下情况:
1 2 3 4 5 TCP 127.0.0.1:5037 0.0.0.0:0 LISTENING 5596 TCP 127.0.0.1:5037 127.0.0.1:49508 ESTABLISHED 5596 TCP 127.0.0.1:5037 127.0.0.1:50671 TIME_WAIT 0 TCP 127.0.0.1:5037 127.0.0.1:50672 TIME_WAIT 0 TCP 127.0.0.1:5037 127.0.0.1:50673 TIME_WAIT 0
从上面的 “TCP 127.0.0.1:5037 127.0.0.1:49508 ESTABLISHED 5596” 可以看出进程5596占用了端口,这时找到5596,并关掉它就可以了。关掉之前可以先看看是什么进程,查看进程的命令:
关掉进程的命令:
此时再次尝试adb连接Android设备,如果还不行,则使用下面的最后一种方法;
(2)使用USB连接电脑,然后执行以下命令行:
在没有报错的前提下,断开USB,再使用命令:
此时就能连接Android设备了,如果还不行,对不起,我也没办法了!
themes设置相关 1 2 3 4 5 6 7 8 9 10 11 12 13 colorPrimary : 顧名思義,就是主要的顏色,這個通常指得是 App 本身產品的代表色,通常也是品牌的主要視覺色 colorPrimaryVariant:主要顏色的變體,通常會從 colorPrimary 往較淡或較濃的色澤 colorOnPrimary:字面意思就是主要顏色上頭的顏色,這個顏色通常使用在背景色是主要顏色的元件上頭(例如字樣 Label 、icon 等) colorSecondary:app 次要的品牌顏色,這些用於裝飾某些特定需要的 widget colorSecondaryVariant:次要顏色的變體,也就是次要顏色偏暗或偏亮的樣式 colorOnSecondary:用於顯示於次要顏色上元件的顏色 colorError:顯示錯誤的顏色 (最常見的就是紅色) colorOnError:在錯誤顏色上頭元件的顏色 colorSurface:表層顏色(就是 Sheet 的顏色) colorOnSurface:在表層顏色上的的元件顏色 android:colorBackground:最底的背景色 colorOnBackground:用於對底背景色上頭的元件用的顏色 利用这些属性,搭配上面的那些技巧,可以組合出很棒的效果。
.bridge
是让按钮等不默认使用主题色,.NoActionBar
是不显示顶部导航条,现在用Toolbar
替代ActionBar
了,要用自带标题栏就用Toolbar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <resources xmlns:tools ="http://schemas.android.com/tools" > <style name ="Theme.MyApplication" parent ="Theme.MaterialComponents.DayNight.DarkActionBar" > <item name ="colorPrimary" > @color/purple_500</item > <item name ="colorPrimaryVariant" > @color/purple_700</item > <item name ="colorOnPrimary" > @color/white</item > <item name ="colorSecondary" > @color/teal_200</item > <item name ="colorSecondaryVariant" > @color/teal_700</item > <item name ="colorOnSecondary" > @color/black</item > <item name ="android:statusBarColor" > ?attr/colorPrimaryVariant</item > </style > </resources >
沉侵式体验 沉浸式状态栏
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = getWindow(); window.getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); window.setStatusBarColor(Color.TRANSPARENT); } setContentView(R.layout.activity_main); }
沉浸式导航栏
1 2 3 4 5 6 7 8 9 10 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = getWindow(); window.getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); window.setStatusBarColor(Color.TRANSPARENT); window.setNavigationBarColor(Color.TRANSPARENT); }
使用沉浸式布局
1 2 3 4 5 6 7 8 9 <RelativeLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="match_parent" android:fitsSystemWindows ="true" > </RelativeLayout >
这将会让布局内容在状态栏和导航栏区域留出空间。
透明状态栏 1 2 3 4 5 6 7 8 9 10 11 getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); getWindow().setStatusBarColor(Color.TRANSPARENT); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
属性
作用
View.SYSTEM_UI_FLAG_VISIBLE
状态栏和Activity共存,Activity不全屏显示。也就是应用平常的显示画面
View.SYSTEM_UI_FLAG_FULLSCREEN
Activity全屏显示,且状态栏被覆盖掉,但下拉会出现并且不能再隐藏
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
Activity全屏显示,但是状态栏不会被覆盖掉,而是正常显示,只是Activity顶端布 局会被覆盖住
View.INVISIBLE
Activity全屏显示,隐藏状态栏
隐藏ActionBar 1 getSupportActionBar().hide();
隐藏导航栏 1 getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
依赖设置 1 2 jcenter() maven { url 'https://www.jitpack.io' }
1 implementation 'com.google.android.material:material:1.1.0'
全屏手势 1 2 3 4 5 6 7 getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
属性
作用
View.SYSTEM_UI_FLAG_VISIBLE
状态栏和Activity共存,Activity不全屏显示。也就是应用平常的显示画面
View.SYSTEM_UI_FLAG_FULLSCREEN
Activity全屏显示,且状态栏被覆盖掉
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
Activity全屏显示,但是状态栏不会被覆盖掉,而是正常显示,只是Activity顶端布 局会被覆盖住
View.INVISIBLE
Activity全屏显示,隐藏状态栏
设置图标 第一种:(最简单的方法)
将你准备好的 图标放入res目录下的drawable,在AndroidManifest.xml 文件中,找到android:icon以及android:roundIcon这两个属性,设置为你放入的图标文件。
在这里,这两个属性都能对图标进行设置,在设置时只使用一个也可以达到效果,但如果两个同时使用的话,属性指定的对象需要设置一致。若不一致,我测试结果是显示的roundIcon指定的对象,找到android:roundIcon 属性的解释:
android:roundIcon 属性指定一个图标,但只有你需要给应用设置一个特别的圆形图标时才要用到这个属性。
第二种:(稍微复杂)
更详细的解释可以看这两篇文章
https://www.jb51.net/article/188580.htm
[Android神兵利器之Image Asset Studio]
https://www.jb51.net/article/138346.htm
[application中 android:icon 和 android:roundIcon 的区别]
使用外部字体 在src/main
中创建了一个文件夹assets
文件夹,在assets
文件夹中创建fonts
文件夹,把字体文件放到fonts
目录下
此外,在具有以下内容的同一文件夹内创建一个名为fonts.xml的文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 <font-family xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" > <font app:fontStyle="normal" app:fontWeight="700" app:font="@font/customfont" /> <font app:fontStyle="normal" app:fontWeight="700" app:font="@font/customfont_bold" /> </font-family>
然后,编辑文件app> src> res> values> styles.xml,为整个应用程序应用默认字体。
1 2 3 4 5 6 7 <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar" > <item name="colorPrimary" >@color /colorPrimary</item> <item name="colorPrimaryDark" >@color /colorPrimaryDark</item> <item name="colorAccent" >@color /colorAccent</item> <item name="android:fontFamily" >@font /customfont</item> </style>
如果要更改单个UI元素的字体,请执行以下操作:
1 2 3 4 5 6 7 8 <TextView android:fontFamily="@font/customfont" android:layout_height="wrap_content" android:layout_width="wrap_content" android:textColor="@color/black" android:textSize="18sp" android:text="Some text" />
java中使用
1 2 3 TextView tv=(TextView)findViewById(R.id.custom); Typeface face=Typeface.createFromAsset(getAssets(),"fonts/Verdana.ttf" ); tv.setTypeface(face);
使用内置绘制矢量图 在Project
模式下右击 选择src
,然后选择New/Vector Asset
双击退出 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @SuppressLint("WrongConstant") @Override public boolean onKeyUp (int keyCode, KeyEvent event) { long tempTime = System.currentTimeMillis(); if (keyCode == KeyEvent.KEYCODE_BACK) { if (tempTime - startTime >= 2000 ) { startTime = tempTime; Toast.makeText(this , "再点击一次退出应用" , Toast.LENGTH_SHORT).show(); return true ; } else { System.exit(0 ); } } return super .onKeyUp(keyCode, event); }
网络请求
OKHttp
Retrofit(对OKHttp的封装,下面的Retrofit2的库包含了OKHttp)
1 2 3 implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation "com.squareup.retrofit2:converter-gson:2.9.0"
布局 布局代码过长时可以把一部分代码放带到另一个xml文件中,然后在要引用的地方<include layout="@layout/tab1"/>
一些技巧,把一个控件放在底部时,要让上面的整个界面布满可以用android:layout_weight="1"
属性,如在fragment和viewpager
使用时
控件 设置控件可见与不可见 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 android:visibility="visible" ; android:visibility="invisible" ; android:visibility="gone" ; view.setVisibility(View.VISIBLE); view.setVisibility(View.INVISIBLE); view.setVisibility(View.GONE);
View的一些公共属性
android:id="@+id/tv_hello"
注dp
是与设备无关的属性,px
像素就不一样了,sp
是给字体用的
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="right|center_vertical"
//gravity属性字面意思重力的意思,控制view里面的内容的,多个属性用竖线分隔
android:layout_gravity="center_vertical"
//这个是来控制这个控件在父布局中的位置的,也会由父布局的特性影响到
android:background="@color/teal_200"
padding和margin
自定义背景
新建Drawable Resource File文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?xml version="1.0" encoding="utf-8" ?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <corners android:radius="20dp" /> <stroke android:color="@color/teal_200" android:width="2dp" /> <solid android:color="@color/teal_200" /> </shape>
Dialog(对话框) 1. 最简单的提示对话框
(1)创建AlertDialog.Builder
实例对象。
(2)通过Builder
对象设置对话框的属性。
setTitle()
设置标题
setIcon ()
设置图标
setMessage ()
设置要显示的内容
setItems()
设置在对话框中显示的项目列表
setView()
设置自定义的对话框样式
setPositiveButton ()
设置确定按钮
setNegativeButton ()
设置取消按钮
setNeutralButton ()
设置中立按钮
setSingleChoiceItems
单选框
setMultiChoiceItems
复选框
(3)调用Builder
对象的create()
方法创建AlertDialog
对话框
(4)调用AlertDialog
的show()
方法来显示对话框
(5)调用AlertDialog
的dimiss()
方法销毁对话框。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 AlertDialog.Builder builder= new AlertDialog .Builder(this ).setMessage("是否要修改个人信息" ) .setPositiveButton("确定" , new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialogInterface, int i) { isConfirm = 1 ; dialogInterface.dismiss(); Toast.makeText(ChangeMessageActivity.this , "个人信息保存成功!" , Toast.LENGTH_SHORT).show(); } }) .setNegativeButton("取消" , new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); } }); AlertDialog alertDialog = builder.create(); alertDialog.show();
2. 列表对话框(供选择)
1 2 3 4 5 6 7 8 9 10 11 12 String[] s = new String []{"第一条" ,"第二条" ,"第三条" }; AlertDialog.Builder builder= new AlertDialog .Builder(this ) .setItems(s, new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialogInterface, int i) { Toast.makeText(ChangeMessageActivity.this , "选择了第" +i+1 +"条消息" , Toast.LENGTH_SHORT).show(); } }); AlertDialog alertDialog = builder.create(); alertDialog.show();
3. 单选列表对话框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 String[] s = new String []{"第一条" ,"第二条" ,"第三条" }; AlertDialog.Builder builder= new AlertDialog .Builder(this ) .setSingleChoiceItems(s, 0 , new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialogInterface, int i) { isConfirm = i; } }) .setPositiveButton("确定" , new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialogInterface, int i) { Toast.makeText(ChangeMessageActivity.this , "选择了第" +i+1 +"条条目" , Toast.LENGTH_SHORT).show(); dialogInterface.dismiss(); } }) .setNegativeButton("取消" , new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); } }); AlertDialog alertDialog = builder.create(); alertDialog.show();
4. 多选列表对话框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 String[] s = new String []{"第一条" ,"第二条" ,"第三条" }; boolean [] isCheck = new boolean [3 ]; AlertDialog.Builder builder= new AlertDialog .Builder(this ) .setMultiChoiceItems(s, isCheck, new DialogInterface .OnMultiChoiceClickListener() { @Override public void onClick (DialogInterface dialogInterface, int i, boolean b) { isCheck[i] = b; } }) .setPositiveButton("确定" , new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialogInterface, int i) { for (int j = 0 ; j < isCheck.length; j++) { if (isCheck[j]){ back+=j+1 ; } } Toast.makeText(ChangeMessageActivity.this , "选择了" +back, Toast.LENGTH_SHORT).show(); } }) .setNegativeButton("取消" , new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); } }); AlertDialog alertDialog = builder.create(); alertDialog.show();
5. 半自定义消息框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 View view = getLayoutInflater().inflate(R.layout.half_dialog_view, null ); final EditText editText = (EditText) view.findViewById(R.id.dialog_edit); AlertDialog dialog = new AlertDialog .Builder(this ) .setIcon(R.mipmap.icon) .setTitle("半自定义对话框" ) .setView(view) .setNegativeButton("取消" , new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialog, int which) { dialog.dismiss(); } }) .setPositiveButton("确定" , new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialog, int which) { String content = editText.getText().toString(); Toast.makeText(MainActivity.this , content, Toast.LENGTH_SHORT).show(); dialog.dismiss(); } }).create(); dialog.show();
6. 全自定义消息框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <!--对话框的样式--> <style name="NormalDialogStyle" > <!--对话框背景 --> <item name="android:windowBackground" >@android :color/transparent</item> <!--边框 --> <item name="android:windowFrame" >@null </item> <!--没有标题 --> <item name="android:windowNoTitle" >true </item> <!-- 是否浮现在Activity之上 --> <item name="android:windowIsFloating" >true </item> <!--背景透明 --> <item name="android:windowIsTranslucent" >false </item> <!-- 是否有覆盖 --> <item name="android:windowContentOverlay" >@null </item> <!--进出的显示动画 --> <item name="android:windowAnimationStyle" >@style /normalDialogAnim</item> <!--背景变暗--> <item name="android:backgroundDimEnabled" >true </item> </style> <!--对话框动画--> <style name="normalDialogAnim" parent="android:Animation" > <item name="@android:windowEnterAnimation" >@anim /normal_dialog_enter</item> <item name="@android:windowExitAnimation" >@anim /normal_dialog_exit</item> </style>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 private void customDialog () { final Dialog dialog = new Dialog (this , R.style.NormalDialogStyle); View view = View.inflate(this , R.layout.dialog_normal, null ); TextView cancel = (TextView) view.findViewById(R.id.cancel); TextView confirm = (TextView) view.findViewById(R.id.confirm); dialog.setContentView(view); dialog.setCanceledOnTouchOutside(true ); view.setMinimumHeight((int ) (ScreenSizeUtils.getInstance(this ).getScreenHeight() * 0.23f )); Window dialogWindow = dialog.getWindow(); WindowManager.LayoutParams lp = dialogWindow.getAttributes(); lp.width = (int ) (ScreenSizeUtils.getInstance(this ).getScreenWidth() * 0.75f ); lp.height = WindowManager.LayoutParams.WRAP_CONTENT; lp.gravity = Gravity.CENTER; dialogWindow.setAttributes(lp); cancel.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { dialog.dismiss(); } }); confirm.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { dialog.dismiss(); } }); dialog.show(); }
一个常用设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/transparent" android:orientation="vertical" > <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/round_corner" android:text="拍照" /> <TextView android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:background="#ddd" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/round_corner" android:text="相册" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="@drawable/round_corner" android:text="取消" /> <View android:layout_width="match_parent" android:layout_height="15dp" /> </LinearLayout>
1 2 3 4 5 6 7 8 9 10 11 12 Dialog dialog = new Dialog (this , R.style.NormalDialogStyle);View view = View.inflate(this , R.layout.dialog_bottom, null );dialog.setContentView(view); dialog.setCanceledOnTouchOutside(true ); view.setMinimumHeight((int ) (ScreenSizeUtils.getInstance(this ).getScreenHeight() * 0.23f )); Window dialogWindow = dialog.getWindow();WindowManager.LayoutParams lp = dialogWindow.getAttributes(); lp.width = (int ) (ScreenSizeUtils.getInstance(this ).getScreenWidth() * 0.9f ); lp.height = WindowManager.LayoutParams.WRAP_CONTENT; lp.gravity = Gravity.BOTTOM; dialogWindow.setAttributes(lp); dialog.show();
7. 进度条对话框
1 2 3 4 ProgressDialog dialog = new ProgressDialog (this ); dialog.setMessage("正在加载中" ); dialog.show();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 final ProgressDialog dialog = new ProgressDialog (this ); dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); dialog.setMessage("正在加载中" ); dialog.setMax(100 ); final Timer timer = new Timer (); timer.schedule(new TimerTask () { int progress = 0 ; @Override public void run () { dialog.setProgress(progress += 5 ); if (progress == 100 ) { timer.cancel(); } } }, 0 , 1000 ); dialog.show();
8. 日期和时间选择消息框
1 2 3 4 5 6 7 8 9 Calendar calendar = Calendar.getInstance();new DatePickerDialog (this , new DatePickerDialog .OnDateSetListener() { @Override public void onDateSet (DatePicker view, int year, int month, int dayOfMonth) { } }, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)) .show();
TextView 空格及其他占位符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ``` ``` == ==   == no-break space (普通的英文半角空格但不换行) ```  ```== 中文全角空格 (一个中文宽度) ``` ``` ==   == en空格 (半个中文宽度) ```  ```==   == em空格 (一个中文宽度) ``` ``` == 四分之一em空格 (四分之一中文宽度) 相比平时的空格( ),nbsp拥有不间断(non-breaking)特性。即连续的nbsp会在同一行内显示。即使有100个连续的nbsp,浏览器也不会把它们拆成两行。 ```java //设置颜色 mTextView2.setTextColor(getResources().getColor(R.color.black));
1 2 3 4 StringBuffer stringBuffer = new StringBuffer (); stringBuffer.append(year); stringBuffer.append("111" );
1 2 3 4 5 tv = (TextView) findViewById(R.id. text_view ); tv.getPaint().setFlags(Paint.STRIKE_THRU_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG ); tv .getPaint().setFlags(Paint. UNDERLINE_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG );
1 2 android:maxLines="2" android:ellipsize="end"
1 2 3 4 5 6 7 8 9 10 11 12 13 edtTitle.setOnTouchListener(new View .OnTouchListener() { @Override public boolean onTouch (View view, MotionEvent motionEvent) { if (motionEvent.getX() >= (view.getWidth() - view.getPaddingEnd() - edtTitle.getCompoundDrawables()[2 ].getBounds().width())) { edtTitle.setText("" ); return true ; } return false ; } });
Edittext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 android:inputType="none" android:inputType="text" android:inputType="textCapCharacters" android:inputType="textCapWords" android:inputType="textCapSentences" android:inputType="textAutoCorrect" android:inputType="textAutoComplete" android:inputType="textMultiLine" android:inputType="textImeMultiLine" android:inputType="textNoSuggestions" android:inputType="textUri" android:inputType="textEmailAddress" android:inputType="textEmailSubject" android:inputType="textShortMessage" android:inputType="textLongMessage" android:inputType="textPersonName" android:inputType="textPostalAddress" android:inputType="textPassword" android:inputType="textVisiblePassword" android:inputType="textWebEditText" android:inputType="textFilter" android:inputType="textPhonetic" android:inputType="number" android:inputType="numberSigned" android:inputType="numberDecimal" android:inputType="phone" android:inputType="datetime" android:inputType="date" android:inputType="time"
maxlength
控制读入最大长度
maxline
控制显示最大行数
去掉下滑线background="@null"
textAlignment
设置输入居中
EditText触点问题
setFocusable这个是用键盘是否能获得焦点 setFocusableInTouchMode这个是触摸是否能获得焦点
记事本类似实例,大范围点击即可获取某个输入框的焦点输入,并在键盘收起后失去焦点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:3.0.0-RC3' RelativeLayout relativeLayout = findViewById(R.id.relativelayout); mEditText = findViewById(R.id.edittext_message); relativeLayout.setOnClickListener(new View .OnClickListener() { @SuppressLint("NewApi") @Override public void onClick (View view) { mEditText.requestFocus(); InputMethodManager inputMethodManager = (InputMethodManager) mEditText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.showSoftInput(mEditText,0 ); } }); KeyboardVisibilityEvent.setEventListener(GetActivity.this , new KeyboardVisibilityEventListener () { @Override public void onVisibilityChanged (boolean b) { if (!b){ mEditText.clearFocus(); } } }); setFocusable()这个是用键盘是否能获得焦点 setFocusableInTouchMode()这个是触摸是否能获得焦点
Edittext监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 editText.addTextChangedListener(new TextWatcher () { @Override public void onTextChanged (CharSequence text, int start, int before, int count) { } @Override public void beforeTextChanged (CharSequence text, int start, int count,int after) { } @Override public void afterTextChanged (Editable edit) { } });
ImageView android:scaleType=“center” (1)当图片大于ImageView的宽高:以图片的中心点和ImageView的中心点为基准,按照图片的原大小居中显示,不缩放,用ImageView的大小截取图片的居中部分。
(2)当图片小于ImageView的宽高:直接居中显示该图片。
2 android:scaleType=“centerCrop”
(1)当图片大于ImageView的宽高:以图片的中心点和ImageView的中心点为基准,按比例缩小图片,直到图片的宽高有一边等于ImageView的宽高,则对于另一边,图片的长度大于或等于ImageView的长度,最后用ImageView的大小居中截取该图片。
(2)当图片小于ImageView的宽高:以图片的中心店和ImageView的中心点为基准,按比例扩大图片,直到图片的宽高大于或等于ImageView的宽高,并按ImageView的大小居中截取该图片。
3 android:scaleType=“centerInside”
(1)当图片大于ImageView的宽高:以图片的中心和ImageView的中心点为基准,按比例缩小图片,使图片宽高等于或者小于ImagevView的宽高,直到将图片的内容完整居中显示。 (2)当图片小于ImageView的宽高:直接居中显示该图片。
4 android:scaleType=“fitCenter”
表示把图片按比例扩大(缩小)到ImageView的宽度,居中显示。 5 android:scaleType=“fitStart”
表示把图片按比例扩大(缩小)到ImageView的宽度,在ImageView的上方显示。
6 android:scaleType=“fitEnd”
表示把图片按比例扩大(缩小)到ImageView的宽度,在ImageView的下方显示。
7 android:scaleType=“fitXY”
表示把图片按指定的大小在ImageView中显示,拉伸或收缩图片,不保持原比例,填满ImageView。 8 android:scaleType=“matrix” 表示不改变原图的大小,从ImageView的左上角开始绘制原图,原图超过ImageView的部分作裁剪处理。 用矩阵来绘制,动态缩小放大图片来显示。
1 2 3 4 android:textAllCaps="false //取消Button边框 style=" ?android:attr/borderlessButtonStyle"
RadioButton和RadioGroup(本质是线性布局)
单选选择框,搭配RadioGroup 使用,RadioButton通过android:checked
属性判断是否选中,一开始都为不选中状态,一旦选中将无法取消,只能选择其他的,但个人感觉可以一开始设计选择第一个,如果用户不选择则默认选择第一个,这样子还可以避免判断是否选择的情况,但不知道交互上哪个更好
RadioGroup中一个按钮被选中后checked属性将为true,其他的button属性为false,android:button = ""
属性设为@null则没有圆点了
要根据是否选中改变RadioButton的drawableTop的图片可以新建一个drawable文件设置给这个属性,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0" encoding="utf-8" ?> <selector xmlns:android="http://schemas.android.com/apk/res/android" > <item android:drawable="@drawable/home_icon_normal" android:state_checked="false" /> <item android:drawable="@drawable/home_icon_checked" android:state_checked="true" /> </selector> android:drawableTop="@drawable/btn1_background" <?xml version="1.0" encoding="utf-8" ?> <selector xmlns:android="http://schemas.android.com/apk/res/android" > <item android:color="@color/black" android:state_checked="false" /> <item android:color="@color/blue_02C4FF" android:state_checked="true" /> </selector>
使用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <RadioGroup android:id="@+id/radio_group_gender" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_alignStart="@id/edittext_id" android:layout_below="@id/textview_gander" > <RadioButton android:id="@+id/radiobutton_gender_man" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="男" android:textSize="20sp" /> <RadioButton android:id="@+id/radiobutton_gender_woman" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="女" android:textSize="20sp" android:layout_marginStart="25dp" /> </RadioGroup> private int gender; RadioGroup radioGroup_gander = findViewById(R.id.radio_group_gender); radioGroup_gender.setOnCheckedChangeListener(this ); @SuppressLint("NonConstantResourceId") @Override public void onCheckedChanged (RadioGroup radioGroup, int i) { switch (i){ case R.id.radiobutton_gender_man: gender = 0 ; break ; case R.id.radiobutton_gender_woman: gender = 1 ; break ; default : gender = -1 ; } } private int gender; RadioButton radioButton_gender_man = findViewById(R.id.radiobutton_gender_man); RadioButton radioButton_gender_woman = findViewById(R.id.radiobutton_gender_woman); if (radioButton_gender_man.isChecked()){ gender = 0 ; } else if (radioButton_gender_woman.isChecked()){ gender = 1 ; } else gender = -1 ;
自定义
一个为灰色边框内部为白色,点击为蓝色打勾
1 2 3 4 5 <?xml version="1.0" encoding="utf-8" ?> <selector xmlns:android ="http://schemas.android.com/apk/res/android" > <item android:state_checked ="true" android:drawable ="@drawable/bg_rbtn_activate_membership_selected" /> <item android:state_enabled ="false" android:drawable ="@drawable/bg_rbtn_activate_membership_normal" /> </selector >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="utf-8" ?> <layer-list xmlns:android ="http://schemas.android.com/apk/res/android" > <item android:drawable ="@drawable/bg_rbtn_activate_membership_normal_bottom" /> <item > <shape android:shape ="oval" > <stroke android:color ="@color/gray_979797" android:width ="1dp" /> </shape > </item > </layer-list >
1 2 3 4 5 <vector android:height ="24dp" android:tint ="#FFFFFF" android:viewportHeight ="24" android:viewportWidth ="24" android:width ="24dp" xmlns:android ="http://schemas.android.com/apk/res/android" > <path android:fillColor ="@android:color/white" android:pathData ="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2z" /> </vector >
1 2 3 4 5 <vector android:height ="24dp" android:tint ="#3C85FF" android:viewportHeight ="24" android:viewportWidth ="24" android:width ="24dp" xmlns:android ="http://schemas.android.com/apk/res/android" > <path android:fillColor ="@android:color/white" android:pathData ="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z" /> </vector >
Checkbox
支持多选的选择框
万能RecyclerView GitHub
https://github.com/youlookwhat/ByRecyclerView/wiki
ListView
和 GridView
的加强版
显示大量数据,减少内存占用量
类ListVeiw功能
先在布局文件添加RecyclerView
控件,然后在Activity中拿到控件
然后准备数据,可以通过活动传给Adapter,也可以直接在Adapter里面直接准备,一般以第一种方式实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Item { private int image; private String message; public int getImage () { return image; } public void setImage (int image) { this .image = image; } public String getMessage () { return message; } public void setMessage (String message) { this .message = message; } }
1 2 3 4 5 6 7 8 9 10 List<Item> list = new ArrayList <>(); for (int i = 0 ; i < 30 ; i++) { Item item = new Item (); item.setImage(R.mipmap.student); item.setMessage("这是第" +i+"个条目" ); list.add(item); } mAdapter.getData(list);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <ImageView android:id="@+id/imageview_item" android:layout_width="100dp" android:layout_height="100dp" android:scaleType="fitXY" android:padding="10dp" android:src="@mipmap/student" /> <TextView android:id="@+id/textview_item" android:layout_marginStart="20dp" android:layout_width="250dp" android:layout_height="100dp" android:textSize="20sp" android:textStyle="bold" android:gravity="center" android:padding="10dp" android:text="条目" /> </LinearLayout> </RelativeLayout>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public class RecyclerViewAdapter extends RecyclerView .Adapter<RecyclerViewAdapter.ViewHolder> { private List<Item> list = new ArrayList <>(); public void getData (List<Item> list) { this .list = list; } @NonNull @Override public ViewHolder onCreateViewHolder (@NonNull ViewGroup parent, int viewType) { ViewHolder viewHolder = new ViewHolder (View.inflate(parent.getContext(), R.layout.item, null )); return viewHolder; } @Override public void onBindViewHolder (@NonNull ViewHolder holder, int position) { holder.mImageView.setImageResource(list.get(position).getImage()); holder.mTextView.setText(list.get(position).getMessage()); } @Override public int getItemCount () { if (list!=null ){ return list.size(); } return 0 ; } public class ViewHolder extends RecyclerView .ViewHolder { private ImageView mImageView; private TextView mTextView; public ViewHolder (@NonNull View itemView) { super (itemView); mImageView = itemView.findViewById(R.id.imageview_item); mTextView = itemView.findViewById(R.id.textview_item); } } }
1 2 3 4 5 6 7 RecyclerView recyclerView = findViewById(R.id.recyclerview); LinearLayoutManager layoutManager = new LinearLayoutManager (this ); recyclerView.setLayoutManager(layoutManager); mAdapter = new RecyclerViewAdapter (); intiData(); recyclerView.setAdapter(mAdapter);
DatePicker(时间选择器)
属性
描述和使用
android:datePickerMode=""
时间选择器有spinner
和calendar
两种模式,这个属性是用来设置模式的,spinner
是直接选择日期,没有日历,calendar
是带日历模式
android:calendarViewShown=""
这个属性是设置日历是否可见的,有true
和false
可选,和模式一起设置成自己想要的,貌似有冲突
android:spinnersShown=""
这个属性是设置直接选择的框是否可见,有true
和false
可选,和模式一起设置成自己想要的,貌似有冲突
android:calendarTextColor=""
设置日历列表文字的颜色
android:dayOfWeekTextAppearance
顶部星期几的文字颜色
android:endYear
去年(内容)
android:firstDayOfWeek
设置日历列表以星期几开头
android:headerBackground
整个头部的背景颜色
android:headerDayOfMonthTextAppearance
头部日期字体的颜色
android:headerMonthTextAppearance
头部月份的字体颜色
android:headerYearTextAppearance
头部年的字体颜色
android:maxDate
最大日期显示在这个日历视图mm / dd / yyyy格式
android:minDate
最小日期显示在这个日历视图mm / dd / yyyy格式
android:yearListItemTextAppearance
列表的文本出现在列表中。
android:yearListSelectorColor
年列表选择的颜色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?xml version="1.0" encoding="utf-8" ?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <DatePicker android:id="@+id/datePickerView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:calendarViewShown="true" android:datePickerMode="calendar" android:layout_gravity="center_horizontal" /> </android.support.constraint.ConstraintLayout>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.example.user.datapicker;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.widget.DatePicker;import android.widget.Toast;import java.util.Calendar;public class MainActivity extends AppCompatActivity { DatePicker datePicker; int year,mouth,day,hour,min; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); datePicker = (DatePicker)findViewById(R.id.datePickerView); Calendar cal = Calendar.getInstance(); year = cal.get(Calendar.YEAR); mouth = cal.get(Calendar.MONTH); day = cal.get(Calendar.DAY_OF_MONTH); hour = cal.get(Calendar.HOUR); min = cal.get(Calendar.MINUTE); datePicker.init(year, mouth, day, new DatePicker .OnDateChangedListener() { @Override public void onDateChanged (DatePicker view, int year, int monthOfYear, int dayOfMonth) { MainActivity.this .year = year; MainActivity.this .mouth = monthOfYear; MainActivity.this .day = dayOfMonth; Toast.makeText(MainActivity.this ,year + "年" + monthOfYear + "月" + dayOfMonth + "日" ,Toast.LENGTH_LONG).show(); } }); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 DatePicker datePicker = findViewById(R.id.date_picker); year = datePicker.getYear(); month = datePicker.getMonth(); day = datePicker.getDayOfMonth(); StringBuffer date = new StringBuffer (); date.append(year); date.append("年" ); date.append(month + 1 ); date.append("月" ); date.append(day); date.append("日" );
####ViewPager2(轮播图)
GitHub
https://github.com/zhpanvip/BannerViewPager/wiki/06.快速开始
//轮播图
implementation 'com.github.zhpanvip:bannerviewpager:3.5.11
//依赖不能按照作者那里写latestVersion,Item的布局必须是”match_parent,嵌套在ScrollView里面会变形
https://github.com/zhpanvip/viewpagerindicator
//轮播图指示器
滑动视图或者滑动Fragment
放Fragment继承FragmentStateAdapter
,放图片继承RecyclerView.Adapter<ViewHolder>
,监听方法registerOnPageChangeCallback
(26条消息) 学不动也要学! ViewPager2新特性_程序员巴士的博客-CSDN博客_viewpager2滑动监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 implementation "androidx.viewpager2:viewpager2:1.0.0" public class ViewPagerAdapter extends RecyclerView .Adapter<ViewPagerAdapter.ViewHolder> { private List<Integer> list = new ArrayList <>(); public void getData (List<Integer> list) { this .list = list; } @NonNull @Override public ViewHolder onCreateViewHolder (@NonNull ViewGroup parent, int viewType) { ViewHolder viewHolder = new ViewHolder (LayoutInflater.from(parent.getContext()).inflate(R.layout.item_viewpager,parent,false )); return viewHolder; } @Override public void onBindViewHolder (@NonNull ViewHolder holder, int position) { holder.mImageView.setImageResource(list.get(position%list.size())); } @Override public int getItemCount () { if (list!=null ){ return Integer.MAX_VALUE; } return 0 ; } public class ViewHolder extends RecyclerView .ViewHolder { ImageView mImageView; public ViewHolder (@NonNull View itemView) { super (itemView); mImageView = itemView.findViewById(R.id.imageview); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?xml version="1.0" encoding="utf-8" ?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".SecondActivity" > <androidx.viewpager2.widget.ViewPager2 android:id="@+id/viewpager2" android:layout_width="300dp" android:layout_height="200dp" android:layout_centerInParent="true" /> <LinearLayout android:id="@+id/linear" android:layout_width="wrap_content" android:layout_height="40dp" android:layout_below="@id/viewpager2" android:layout_centerHorizontal="true" android:orientation="horizontal" /> </RelativeLayout>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 public class SecondActivity extends AppCompatActivity { private Handler handler; private ViewPager2 mViewPager2; private Runnable mRunnable; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_second); initView(); } @SuppressLint("UseCompatLoadingForDrawables") private void initView () { LinearLayout linear = findViewById(R.id.linear); mViewPager2 = findViewById(R.id.viewpager2); ViewPagerAdapter viewPagerAdapter = new ViewPagerAdapter (); List<Integer> list = new ArrayList <>(); list.add(R.mipmap.pic1); list.add(R.mipmap.pic2); list.add(R.mipmap.pic3); list.add(R.mipmap.pic4); list.add(R.mipmap.pic5); for (int i = 0 ; i < list.size(); i++) { LinearLayout.LayoutParams layoutParams = new LinearLayout .LayoutParams(40 ,40 ); View view = new View (this ); view.setBackground(getDrawable(R.drawable.point_viewpager_normal)); view.setLayoutParams(layoutParams); layoutParams.setMarginStart(20 ); linear.addView(view); } mViewPager2.registerOnPageChangeCallback(new ViewPager2 .OnPageChangeCallback() { @Override public void onPageScrolled (int position, float positionOffset, int positionOffsetPixels) { super .onPageScrolled(position, positionOffset, positionOffsetPixels); for (int i = 0 ; i < list.size(); i++) { if (i==position%list.size()){ linear.getChildAt(i).setBackground(getDrawable(R.drawable.point_viewpager_checked)); }else { linear.getChildAt(i).setBackground(getDrawable(R.drawable.point_viewpager_normal)); } } } }); viewPagerAdapter.getData(list); mViewPager2.setAdapter(viewPagerAdapter); mViewPager2.setCurrentItem(200 ,false ); mViewPager2.setUserInputEnabled(false ); handler = new Handler (); mRunnable = ()->{ int current = mViewPager2.getCurrentItem(); mViewPager2.setCurrentItem(++current); handler.postDelayed(mRunnable,2000 ); }; } @Override public void onAttachedToWindow () { super .onAttachedToWindow(); handler.postDelayed(mRunnable,1000 ); } @Override public void onDetachedFromWindow () { super .onDetachedFromWindow(); handler.removeCallbacks(mRunnable); } }
TabLayout
属性
用法
app:tabMode="scrollable"
**fixed
固定的,也就是标题不可滑动,无论放了多少个都平分界面长度, scrollable
可滑动的,小于等于5个默认靠左固定,大于5个就可以滑动了, auto
**自动选择是否可以滑动,小于等于5个默认居中,大于5个自动滑动
app:tabGravity
**start
居左, fill
平均分配,铺满屏幕宽度 center
**居中,默认fill,tab填满TabLayout,但tabMode=“fixed”
才生效
<attr name="tabTextColor" format="color"/>
Tab未选中字体颜色
<attr name="tabSelectedTextColor" format="color"/>
Tab选中字体颜色
<attr name="tabMinWidth" format="dimension"/>
Tab最小宽度
attr name="tabIndicatorColor" format="color"
指示器颜色
attr name="tabIndicatorHeight" format="dimension"
指示器高度
attr name="tabIndicatorFullWidth" format="boolean"
指示器宽度 true:和tab同宽 false:和tab中的字同宽
attr name="tabBackground" format="reference"
仅是Tab背景,设置TabLayout背景用android:background
attr name="tabContentStart" format="dimension"
tabs距TabLayout开始位置的偏移量,但app:tabMode="scrollable"
才生效
Java中添加Tab TabLayout.addTab(TabLayout.newTab().setText("222"));
,注意addTab
里面传的参数是控件名.newTab()
Java中设置某个被选中,其他会自动不被选中,代码为TabLayout.getTabAt(1).select();
ProgressBar 进度条
Spinner(下拉选择框,Android微调器) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <Spinner <androidx.appcompat.widget.AppCompatSpinner dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() maven { url "https://jitpack.io" } } } NiceSpinner niceSpinner = (NiceSpinner) findViewById(R.id.nice_spinner); List<String> dataset = new LinkedList <>(Arrays.asList("One" , "Two" , "Three" , "Four" , "Five" )); niceSpinner.attachDataSource(dataset); spinner.setOnSpinnerItemSelectedListener(new OnSpinnerItemSelectedListener () { @Override public void onItemSelected (NiceSpinner parent, View view, int position, long id) { String item = parent.getItemAtPosition(position); ... } });
三种使用方式
第一种:
数据固定的,通过 android:entries
属性引数组来进行填充,在 values
下建一个 arrays.xml
,在这里面填充,或者直接在values包下strings.xml
的resources标签下添加,实例在下面代码框
android:spinnerMode=”dialog”
表示Spinner
的样式是dialog
,android:prompt
可以设置dialog的标题(注意必须在string资源下引用,不然程序会崩掉
1 2 3 4 <string-array name="city_name" > <item>湛江</item> <item>北京</item> </string-array>
第二种:
通过适配器来填充参数,继承BaseAdapter
Android之Spinner用法详解_Android_脚本之家 (jb51.net)
第三种:
实现SpinnerAdaper
接口,其实跟继承BaseAdapter
差不多
设置样式
在values
包下新建style.xml
用来专门保存样式,按钮什么的样式也可以集中放到这里来
不知道什么原因,自带spinner生效不了,nice—spinner可以生效
1 2 3 4 5 6 <style name="CitySpinnerStyle" > <item name="android:divider" >#A9A9A9</item> <item name="android:dividerHeight" >2dp</item> </style>
NestedScrollView能够判断滚动视图完成哪个,滑动顺畅,使用这个即可
Activity相关 android:exported = true
true表示可以被其他Activity启动
生命周期相关
1)横竖屏切换对Activiy生命周期影响 横竖屏切换涉及到的是Activity的android:configChanges属性: android:configChanges可以设置的属性值有: orientation:消除横竖屏的影响 keyboardHidden:消除键盘的影响 screenSize:消除屏幕大小的影响 【情况1】 1、android:configChanges=orientation 2、android:configChanges=orientation|keyboardHidden 3、不设置android:configChanges 横竖屏切换Activity生命周期变化: onPause–>onSaveInstanceState–>onStop–>onDestroy–>onCreate–>onStart–>onRestoreInstanceState–>onResume 在进行横竖屏切换的时候在调用onStop之前会调用onSaveInstanceState来进行Activity的状态保存,随后在重新显示该Activity的onResume方法之前会调用onRestoreInstanceState来恢复之前由onSaveInstanceState保存的Activity信息 【情况2】 1、android:configChanges=orientation|screenSize 2、android:configChanges=orientation|screenSize|keyboardHidden 横竖屏切换不会重新加载Activity的各个生命周期,一定要同时出现orientation和screenSize 【情况3】 屏蔽横竖屏切换操作,不会出现切换的过程中Activity生命周期重新加载的情况 方法1:清单文件 android:screenOrientation=“portrait” 始终以竖屏显示 android:screenOrientation=“landscape” 始终以横屏显示 方法2:动态Activity Activity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);以竖屏显示 Activity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);以横屏显示
跳转传值
普通跳转和跳转网页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Intent intent = new Intent (this ,MainActivity2.class);startActivity(intent); Intent intent = new Intent ();intent.setAction(); intent.addCategory(); intent.setData(); <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter>
带值传递不需要处理返回值
1 2 3 4 5 6 7 8 9 10 11 Intent intent = new Intent (this ,MainActivity2.class);Bundle bundle = new Bundle ();bundle.putInt("result" ,1 ); intent.putExtras(bundle); startActivity(intent); Intent intent = getIntent();Bundle bundle = intent.getExtras();int result = bundle.getInt("result" );
Bundle相关
Bundle在Android开发中非常常见,它的作用主要时用于传递数据。Bundle传递的数据包括:string、int、boolean、byte、float、long、double等基本类型或它们对应的数组,也可以是对象或对象数组。当Bundle传递的是对象或对象数组时,必须实现Serialiable或Parcelable接口。 Bundle所保存的数据是以key-value(键值对)的形式保存在ArrayMap中,处理少量简单数据比hashmap快,处理大量数据用hashmap
Bundle使用场景 Activity之onSaveInstanceState Activity状态数据的保存与恢复,涉及到两个回调: ①void onSaveInstanceState(Bundle outState);② void onCreate(Bundle savedInstanceState); 参考:Android系统之onSaveInstanceState用法及源码分析
Fragment之setArguments Fragment的setArguments方法:void setArgument(Bundle args); 参考:Android系统之Fragment用法
Handle之setData 消息机制中的Message的setData方法:void setData(Bundle data)。 参考:Android系统线程间通信方式之Handler机制
带值传递且需要处理返回值(登陆界面跳转注册界面注册成功带账号密码返回登陆界面)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private ActivityResultLauncher<Intent> intentActivityResultLauncher; intentActivityResultLauncher = registerForActivityResult(new ActivityResultContracts .StartActivityForResult(), result1 -> { int code = result1.getResultCode(); Intent data = result1.getData(); }); Bundle bundle = new Bundle ();bundle.putInt("result" , result); Intent intent = new Intent (LoginActivity.this , RegisterActivity.class);intent.putExtras(bundle); intentActivityResultLauncher.launch(intent); Intent intent = new Intent ();Bundle bundle = new Bundle ();bundle.putString("username" ,username); bundle.putString("password" ,password); intent.putExtras(bundle); setResult(88 ,intent); finish();
传递对象
Serializable进行对象序列化,这种序列化是通过反射机制从而削弱了性能,这种机制创建了大量的临时对象从而会引起GC频繁回收调用资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Person person = new Person (); person.setName("chenjy" ); person.setAge(18 ); Bundle bundle = new Bundle (); bundle.putSerializable("person" ,person); Intent intent = new Intent (MainActivity.this , SecondActivity.class); intent.putExtras(bundle); startActivity(intent); Person person = (Person)getIntent().getSerializableExtra("person" );
Parcelable是由Android提供的序列化接口,google做了大量的优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class Person implements Parcelable { private String name; private int age; public Person () {} protected Person (Parcel in) { name = in.readString(); age = in.readInt(); } public void setName (String name) { this .name = name; } public void setAge (int age) { this .age = age; } public String getName () { return name; } public int getAge () { return age; } public static final Creator<Person> CREATOR = new Creator <Person>() { @Override public Person createFromParcel (Parcel in) { Person person = new Person (); person.name = in.readString(); person.age = in.readInt(); return person; } @Override public Person[] newArray(int size) { return new Person [size]; } }; @Override public int describeContents () { return 0 ; } @Override public void writeToParcel (Parcel dest, int flags) { dest.writeString(name); dest.writeInt(age); } }
传输数据量较大的时候Parcelable会出现异常TransactionTooLargeException。只时候就需要用到插件EventBus。
EventBus 使用的是发布 订阅者模型,发布者通过EventBus发布事件,订阅者通过EventBus订阅事件。当发布者发布事件时,订阅该事件的订阅者的事件处理方法将被调用。(后续使用到再看)
Fragment相关 view.findViewById()
####Fragment和其他控件一起使用实现滑动点击切换
构造Fragment实例
构造Fragment实例的时候可以直接new出来,也可以用生成Fragment自带的newInstance()
方法,这个方法还可以传值,传的值可以直接在Fragment操作
FragmentContainerView控件绑定Fragment和普通按钮布局实现点击切换
点击改变颜色和放置不同图片什么都没什么,主要是怎么切换fragment,切换fragment就只要在点击按钮时改变传入的Fragment实例化对象即可
1 2 3 4 5 6 7 8 FragmentManager fragmentManager = getSupportFragmentManager();FirstFragment fragment = FirstFragment.newInstance("这是首页" ,"" );FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();fragmentTransaction.replace(R.id.fragment_container,fragment).commit();
Fragment和ViewPager2实现滑动切换效果,把Fragment换为颜色去掉底部按键就是滑动
设置可滑动就是微信那种,不可滑动就是QQ那种,注意手势冲突
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 public class ThirdActivity extends AppCompatActivity implements RadioGroup .OnCheckedChangeListener { private ViewPager2 mViewPager2; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_third); initView(); } private void initView () { RadioButton btn1 = findViewById(R.id.btn1); RadioButton btn2 = findViewById(R.id.btn2); RadioButton btn3 = findViewById(R.id.btn3); RadioGroup radioGroup = findViewById(R.id.radio_group); radioGroup.setOnCheckedChangeListener(this ); mViewPager2 = findViewById(R.id.viewpager2); ViewPagerTopAdapter viewPagerTopAdapter = new ViewPagerTopAdapter (this ); viewPagerTopAdapter.getFragment(new FirstFragment ()); viewPagerTopAdapter.getFragment(new SecondFragment ()); viewPagerTopAdapter.getFragment(new ThirdFragment ()); mViewPager2.setAdapter(viewPagerTopAdapter); mViewPager2.setCurrentItem(0 ); mViewPager2.registerOnPageChangeCallback(new ViewPager2 .OnPageChangeCallback() { @Override public void onPageSelected (int position) { super .onPageSelected(position); switch (position){ case 0 : btn1.setChecked(true ); break ; case 1 : btn2.setChecked(true ); break ; case 2 : btn3.setChecked(true ); break ; } } }); } @Override public void onCheckedChanged (RadioGroup radioGroup, int i) { switch (i){ case R.id.btn1: mViewPager2.setCurrentItem(0 ); break ; case R.id.btn2: mViewPager2.setCurrentItem(1 ); break ; case R.id.btn3: mViewPager2.setCurrentItem(2 ); break ; } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".ThirdActivity" > <androidx.viewpager2.widget.ViewPager2 android:id="@+id/viewpager2" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <View android:layout_width="match_parent" android:layout_height="2dp" android:background="@color/gray_dark" /> <RadioGroup android:id="@+id/radio_group" android:layout_width="match_parent" android:layout_height="80dp" android:layout_alignParentBottom="true" android:orientation="horizontal" > <RadioButton android:id="@+id/btn1" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:button="@null" android:checked="true" android:drawableTop="@drawable/btn1_background" android:gravity="center" android:text="首页" android:textColor="@drawable/change_radio_button" /> <RadioButton android:id="@+id/btn2" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:button="@null" android:drawableTop="@drawable/btn2_background" android:gravity="center" android:text="联系人" android:textColor="@drawable/change_radio_button" /> <RadioButton android:id="@+id/btn3" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:button="@null" android:drawableTop="@drawable/btn3_background" android:gravity="center" android:text="设置" android:textColor="@drawable/change_radio_button" /> </RadioGroup> </LinearLayout>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class ViewPagerTopAdapter extends FragmentStateAdapter { private List<Class> list = new ArrayList <>(); public void getFragment (Fragment fragment) { list.add(fragment.getClass()); } public ViewPagerTopAdapter (@NonNull FragmentActivity fragmentActivity) { super (fragmentActivity); } @NonNull @Override public Fragment createFragment (int position) { try { return (Fragment) list.get(position).newInstance(); } catch (IllegalAccessException | InstantiationException e) { e.printStackTrace(); } return null ; } @Override public int getItemCount () { return list.size(); } }
TabLayout+ViewPager2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 package com.example.fragmentstudy;import android.os.Bundle;import androidx.annotation.NonNull;import androidx.annotation.Nullable;import androidx.fragment.app.Fragment;import androidx.viewpager2.widget.ViewPager2;import android.text.style.AlignmentSpan;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import com.example.fragmentstudy.Adapter.ViewPagerTopAdapter;import com.example.fragmentstudy.Fragment.BlankFragment1;import com.example.fragmentstudy.Fragment.BlankFragment2;import com.example.fragmentstudy.Fragment.BlankFragment3;import com.google.android.material.tabs.TabLayout;import java.util.ArrayList;import java.util.List;import java.util.Objects;public class HomeFragment extends Fragment { private ViewPager2 mViewPager2; private TabLayout mTabLayout; public HomeFragment () { } public static HomeFragment newInstance () { HomeFragment fragment = new HomeFragment (); Bundle args = new Bundle (); fragment.setArguments(args); return fragment; } @Override public void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); } @Override public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_home, container, false ); } @Override public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) { super .onViewCreated(view, savedInstanceState); mViewPager2 = view.findViewById(R.id.viewpager2); mTabLayout = view.findViewById(R.id.tab_layout); initTab(); initData(); } private void initData () { ViewPagerTopAdapter viewPagerTopAdapter = new ViewPagerTopAdapter (this .requireActivity()); viewPagerTopAdapter.getFragment(new BlankFragment1 ()); viewPagerTopAdapter.getFragment(new BlankFragment2 ()); viewPagerTopAdapter.getFragment(new BlankFragment3 ()); mViewPager2.setAdapter(viewPagerTopAdapter); mViewPager2.registerOnPageChangeCallback(new ViewPager2 .OnPageChangeCallback() { @Override public void onPageSelected (int position) { super .onPageSelected(position); mTabLayout.getTabAt(position).select(); } }); } private void initTab () { String[] name = {"汽车" , "美食" , "趣闻" }; for (int i = 0 ; i < 3 ; i++) { mTabLayout.addTab(mTabLayout.newTab().setText(name[i])); } mTabLayout.addOnTabSelectedListener(new TabLayout .OnTabSelectedListener() { @Override public void onTabSelected (TabLayout.Tab tab) { mViewPager2.setCurrentItem(tab.getPosition()); } @Override public void onTabUnselected (TabLayout.Tab tab) { } @Override public void onTabReselected (TabLayout.Tab tab) { } }); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" tools:context=".HomeFragment" android:orientation="vertical" > <com.google.android.material.tabs.TabLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="50dp" app:tabMode="auto" /> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/viewpager2" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> </LinearLayout>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class ViewPagerTopAdapter extends FragmentStateAdapter { private List<Class> list = new ArrayList <>(); public void getFragment (Fragment fragment) { list.add(fragment.getClass()); } public ViewPagerTopAdapter (@NonNull FragmentActivity fragmentActivity) { super (fragmentActivity); } @NonNull @Override public Fragment createFragment (int position) { try { return (Fragment) list.get(position).newInstance(); } catch (IllegalAccessException | InstantiationException e) { e.printStackTrace(); } return null ; } @Override public int getItemCount () { return list.size(); } }
玩Android项目开发7—–项目页面(使用ViewPager 2 + TabLayout实现项目页面)_tablayout.mode_auto-CSDN博客
Service服务 这里以一个简单的音乐播放器引入
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?xml version="1.0" encoding="utf-8" ?> <RelativeLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" tools:context =".MainActivity" > <Button android:id ="@+id/btn_play_or_stop" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_centerVertical ="true" android:layout_marginStart ="40dp" android:text ="播放/暂停" android:textSize ="20sp" /> <SeekBar android:id ="@+id/skb_play" android:layout_width ="match_parent" android:paddingStart ="20dp" android:paddingEnd ="20dp" android:layout_height ="wrap_content" android:layout_below ="@id/btn_play_or_stop" android:layout_centerHorizontal ="true" android:layout_marginTop ="20dp" /> </RelativeLayout >
在assests资源文件夹下放一首mp3音乐,命名为test_music.mp3
MyTestMusicService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public class MyTestMusicService extends Service { private MediaPlayer mMediaPlayer; public MyTestMusicService () { } @Override public IBinder onBind (Intent intent) { return new MyBinder (); } @Override public void onCreate () { super .onCreate(); mMediaPlayer = new MediaPlayer (); try { AssetManager assetManager = getAssets(); AssetFileDescriptor assetFileDescriptor = assetManager.openFd("test_music.mp3" ); mMediaPlayer.setDataSource(assetFileDescriptor.getFileDescriptor(), assetFileDescriptor.getStartOffset(), assetFileDescriptor.getLength()); mMediaPlayer.prepare(); } catch (IOException e) { e.printStackTrace(); } Log.i("TAGG" , "准备播放音乐" ); } public class MyBinder extends Binder { public boolean isPlaying () { return mMediaPlayer.isPlaying(); } public void play () { if (isPlaying()) { mMediaPlayer.pause(); Log.i("TAGG" , "音乐暂停播放" ); } else { mMediaPlayer.start(); Log.i("TAGG" , "音乐继续播放" ); } } public int getDuration () { return mMediaPlayer.getDuration(); } public int getCurrentPosition () { return mMediaPlayer.getCurrentPosition(); } public void seekTo (int mesc) { mMediaPlayer.seekTo(mesc); } } }
MainActivity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 public class MainActivity extends AppCompatActivity { private Button btnPlayOrStop; private SeekBar skb; private ServiceConnection mServiceConnection; private MyTestMusicService.MyBinder musicController; private Handler mHandler; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initEvent(); } private void initView () { btnPlayOrStop = findViewById(R.id.btn_play_or_stop); btnPlayOrStop.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View view) { play(); } }); skb = findViewById(R.id.skb_play); skb.setOnSeekBarChangeListener(new SeekBar .OnSeekBarChangeListener() { @Override public void onProgressChanged (SeekBar seekBar, int i, boolean b) { if (b){ musicController.seekTo(i); } } @Override public void onStartTrackingTouch (SeekBar seekBar) { } @Override public void onStopTrackingTouch (SeekBar seekBar) { } }); } private void initEvent () { mHandler = new Handler (getMainLooper(), new Handler .Callback() { @Override public boolean handleMessage (@NonNull Message message) { if (message.what == 0 ) { updatePlayPosition(); } return false ; } }); startMusicService(); } private void startMusicService () { Intent intent = new Intent (this , MyTestMusicService.class); startService(intent); Toast.makeText(this , "启动服务" , Toast.LENGTH_SHORT).show(); bindMusicService(); } private void stopMusicService () { unBindMusicService(); Intent intent = new Intent (this , MyTestMusicService.class); stopService(intent); Toast.makeText(this , "停止服务" , Toast.LENGTH_SHORT).show(); } private void bindMusicService () { Intent intent = new Intent (this , MyTestMusicService.class); if (mServiceConnection == null ) { mServiceConnection = new ServiceConnection () { @Override public void onServiceConnected (ComponentName componentName, IBinder iBinder) { Log.i("TAGG" , "绑定服务" ); musicController = (MyTestMusicService.MyBinder) iBinder; btnPlayOrStop.setText("播放" ); skb.setMax(musicController.getDuration()); skb.setProgress(musicController.getCurrentPosition()); } @Override public void onServiceDisconnected (ComponentName componentName) { } }; bindService(intent, mServiceConnection, BIND_AUTO_CREATE); } else { Toast.makeText(this , "已经绑定服务" , Toast.LENGTH_SHORT).show(); } } private void unBindMusicService () { if (mServiceConnection != null ) { unbindService(mServiceConnection); mServiceConnection = null ; Log.i("TAGG" , "接绑服务" ); } else { Toast.makeText(this , "还未绑定服务" , Toast.LENGTH_SHORT).show(); } } private void updatePlayTest () { if (musicController.isPlaying()) { btnPlayOrStop.setText("播放" ); mHandler.sendEmptyMessageDelayed(0 , 500 ); } else { btnPlayOrStop.setText("暂停" ); } } private void play () { updatePlayTest(); musicController.play(); } private void updatePlayPosition () { skb.setProgress(musicController.getCurrentPosition()); mHandler.sendEmptyMessageDelayed(0 , 500 ); } @Override protected void onResume () { super .onResume(); if (musicController!=null ){ mHandler.sendEmptyMessage(0 ); } } @Override protected void onStop () { super .onStop(); mHandler.removeCallbacksAndMessages(null ); } @Override protected void onDestroy () { super .onDestroy(); if (mServiceConnection != null ) { stopMusicService(); } } }
SharedPreferences 1 2 3 4 5 6 7 8 9 10 SharedPreferences.Editor qq_xml=getSharedPreferences("qq_xml" ,MODE_PRIVATE).edit(); qq_xml.putString("number" ,number_qq); qq_xml.putString("password" ,pass1_qq); qq_xml.apply(); SharedPreferences nw_qq=getSharedPreferences("qq_xml" ,MODE_PRIVATE); String number=nw_qq.getString("number" ,"" ); String pasword=nw_qq.getString("password" ,"" );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.example.databasestudy.Class;import android.content.Context;import android.content.SharedPreferences;import java.util.HashMap;import java.util.Map;public class SP { private SharedPreferences.Editor edit; private SharedPreferences mSharedPreferences; public void getSP (Context context) { mSharedPreferences = context.getSharedPreferences("data_1" , Context.MODE_PRIVATE); edit = mSharedPreferences.edit(); } public void save (String username,String password,String isRememberPassword) { edit.putString("username" ,username); edit.putString("password" ,password); edit.putString("isRememberPassword" ,isRememberPassword); edit.apply(); } public void clear (String isRememberPassword) { if (isRememberPassword.equals("NO" )){ edit.clear(); edit.putString("isRememberPassword" , "NO" ); edit.apply(); } } public Map<String,String> read () { Map<String,String> data = new HashMap <>(); data.put("username" ,mSharedPreferences.getString("username" ,"" )); data.put("password" ,mSharedPreferences.getString("password" ,"" )); data.put("isRememberPassword" ,mSharedPreferences.getString("isRememberPassword" ,"NO" )); return data; } }
SQLite
存储类
描述
NULL
值是一个 NULL 值。
INTEGER
值是一个带符号的整数,根据值的大小存储在 1、2、3、4、6 或 8 字节中。
REAL
值是一个浮点值,存储为 8 字节的 IEEE 浮点数字。
TEXT
值是一个文本字符串,使用数据库编码(UTF-8、UTF-16BE 或 UTF-16LE)存储。
BLOB
值是一个 blob 数据,完全根据它的输入存储。
数据库事务:
安全性、高效性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public long insert (String number, String name, String grade, String gender) { SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); try { ContentValues values = new ContentValues (); values.put("number" ,number); values.put("name" ,name); values.put("grade" ,grade); values.put("gender" ,gender); long l = db.insert(WE,null ,values); db.setTransactionSuccessful(); return l; }catch (Exception e){ throw new RuntimeException ("中途出错!" ); }finally { db.endTransaction(); db.close(); } }
构建传参时可以定死名字工厂和版本,就传界面进行构建
1 2 3 4 5 public static String DB_NAME = "MySQLite.db" ;public SQLite (Context context) { super (context,DB_NAME,null ,1 ); }
1 2 3 4 public SQLite (@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) { super (context, name, factory, version); }
建表语句,一个数据库可以有很多个表,下面还有更新方法,没学
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override public void onCreate (SQLiteDatabase sqLiteDatabase) { String create = "create table " + WE +" (" + "id integer primary key autoincrement," +"number text," +"name text," +"grade ," +"gender text" + ")" ; sqLiteDatabase.execSQL(create); }
1 2 3 4 @Override public void onUpgrade (SQLiteDatabase sqLiteDatabase, int i, int i1) {}
各种操作
增加数据
1 2 3 4 5 6 7 8 9 10 11 12 public long insert (String number, String name, String grade, String gender) { SQLiteDatabase db = getWritableDatabase(); ContentValues values = new ContentValues (); values.put("number" ,number); values.put("name" ,name); values.put("grade" ,grade); values.put("gender" ,gender); return db.insert(WE,null ,values); }
删除数据
1 2 3 4 5 6 7 public int delete (String number) { SQLiteDatabase db = getWritableDatabase(); return db.delete(WE,"number like ?" ,new String []{number}); }
修改数据
1 2 3 4 5 6 7 8 9 10 11 12 13 //change方法返回值为int类型,更改了多少个数据就返回多少 public int change(String number, String name, String grade, String gender){ SQLiteDatabase db = getWritableDatabase(); ContentValues values = new ContentValues();//其实也是键值对 values.put("number",number); values.put("name",name); values.put("grade",grade); values.put("gender",gender); /* * 传入4个参数,第一个为表名,第二个为修改的完整数据,第三个为判断语句,根据什么来查找修改的 * 第四个为要往判断语句里面填充的值,要为String类型的数组*/ return db.update(WE,values,"number like ?",new String[]{number}); }
查找数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @SuppressLint("Range") public List<data> check (String number) { SQLiteDatabase db = getWritableDatabase(); List<data>list = new ArrayList <>(); Cursor cursor = db.query(WE, new String []{"number,name,grade" }, "number like ?" , new String []{number}, null , null , null ); if (cursor!=null ){ while (cursor.moveToNext()){ String number_back = cursor.getString(cursor.getColumnIndex("number" )); String name_back = cursor.getString(cursor.getColumnIndex("name" )); String grade_back = cursor.getString(cursor.getColumnIndex("grade" )); data data = new data (); data.setNumber(number_back); data.setName(name_back); data.setGrade(grade_back); list.add(data); } cursor.close(); } return list; }
LitePal开源库 这里只展示简单用法,要了解更多用法可以去看作者博客,中午网址那里就是哦,中文网址也有相关博客教学
GitHub上LitePal开源库介绍网站:https://github.com/guolindev/LitePal
中文网址:https://blog.csdn.net/guolin_blog/category_9262963.html
配置LitePal
导入jar包,添加依赖
原本是可以直接添加依赖的,但不知道为什么我的项目添加不成功,后面知道了,jcenter
停用了但LitePal是在jcenter
上的导致下载失败,还是直接导入jar包了,添加依赖的操作可以跳过,直接看下面怎么导入jar包的
1 2 3 4 5 6 7 8 9 10 11 dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() jcenter() maven { url 'https://jitpack.io' } } }
1 2 3 4 dependencies { implementation 'org.litepal.guolindev:core:3.2.3' }
配置litepal.xml
右击app
->New
->Folder
->点击Assets Folder
->Finish
然后再在assets
目录下new File
一个litepal.xml文件,接着编辑里面的内容:
配置文件相当简单,<dbname>
用于设定数据库的名字,<version>
用于设定数据库的版本号,<list>
用于设定所有的映射模型,我们稍后就会用到,复制下面的代码到新建的xml文件里面即可
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="utf-8"?> <litepal> <dbname value="NewsDemo" /> <version value="1" /> <list> </list> </litepal>
配置LitePalApplication(下面方式选择一种即可)
1 2 3 4 5 6 7 <manifest> <application android:name="org.litepal.LitePalApplication"//添加此句代码 ... > ... </application> </manifest>
或者:
1 2 3 4 5 6 7 <manifest> <application android:name="org.litepal.MyOwnApplication" ... > ... </application> </manifest>
这种方式需要继承 LitePalApplication
1 2 3 public class MyOwnApplication extends LitePalApplication { ... }
或者:
1 2 3 4 5 6 7 8 9 public class MyOwnApplication extends AnotherApplication { @Override public void onCreate() { super.onCreate(); LitePal.initialize(this); } ... }
使用方法
注:这个类是一个典型的Java Bean类,在这个Student类中我们定义了number、name、gender、grade这些字段,并且生成了getter方法(获取值)和setter方法(赋值),这个类就可以用来建表了,每个字段就是表的一列,这就是对象关系映射最直观的体验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class Student extends LitePalSupport { String number; String name; String gender; String grade; public String getNumber () { return number; } public void setNumber (String number) { this .number = number; } public String getName () { return name; } public void setName (String name) { this .name = name; } public String getGender () { return gender; } public void setGender (String gender) { this .gender = gender; } public String getGrade () { return grade; } public void setGrade (String grade) { this .grade = grade; } }
之后我们把Student类添加到映射模型列表当中,修改litepal.xml
中的代码
<mapping class = "类"/>
,双引号之间的类换成上面讲的包名.类名,如图,这里的<mapping>
标签来声明要配置的映射模型类,注意一定要使用完整的类名,不管有多少模型类需要映射,都用同样的方法配置在<list>
标签下
注意:无论是要对数据进行怎么样的操作都应该要创建数据库,创建好了就是打开数据库,要先调用LitePal.getDatabase();
这个方法才能操作数据库,万万记得
1 2 3 4 5 6 7 8 9 10 Student student = new Student (); student.setNumber(number); student.setName(name); student.setGrade(grade); student.setGender("男" ); student.save();
1 2 3 4 LitePal.deleteAll(Student.class,"number like ?" ,mEditText_number.getText().toString().trim());
修改数据
1 2 3 4 5 6 7 Student student = new Student ();student.setName(name); student.setGrade(grade); student.setGrade("男" ); student.updateAll("number like ?" ,number);
查询数据
1 2 Student student = LitePal.find(Student.class,1 );
1 2 Student student = LitePal.findFirst(Student.class);
1 2 Student student = LitePal.findLast(Student.class);
1 2 3 List<Student>list = LitePal.where("number like ?" ,mEditText_number.getText().toString().trim()) .find(Student.class);
动画 帧动画
在Android中,帧动画的本质是把一组预先准备好的图片循环切换播放,造成一种动画效果,幻灯片快速播放形成电影。
通过xml方式实现
第一步:先把要系列播放的图片导入到项目中
第二步:在drawable
目录下创建animation_flower.xml
文件(文件名随意)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?xml version="1.0" encoding="utf-8" ?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false" > <item android:drawable="@drawable/img01" android:duration="200" /> <item android:drawable="@drawable/img02" android:duration="200" /> <item android:drawable="@drawable/img03" android:duration="200" /> <item android:drawable="@drawable/img04" android:duration="200" /> <item android:drawable="@drawable/img05" android:duration="200" /> </animation-list>
第三步:布局和Activity
1 2 3 4 5 AnimationDrawable animationDrawable = (AnimationDrawable) mImageView.getBackground();mAnimationDrawable.start(); mAnimationDrawable.stop();
通过Java方法形式实现
1 2 3 4 5 6 7 8 9 10 11 12 13 mAnimationDrawable =new AnimationDrawable (); mAnimationDrawable.addFrame(getResources().getDrawable(R.drawable.img01),200 ); mAnimationDrawable.addFrame(getResources().getDrawable(R.drawable.img02),200 ); mAnimationDrawable.addFrame(getResources().getDrawable(R.drawable.img03),200 ); mAnimationDrawable.addFrame(getResources().getDrawable(R.drawable.img04),200 ); mAnimationDrawable.addFrame(getResources().getDrawable(R.drawable.img05),200 ); mAnimationDrawable.addFrame(getResources().getDrawable(R.drawable.img06),200 ); mAnimationDrawable.setOneShot(false ); mImageViewShow.setBackground(mAnimationDrawable); mAnimationDrawable.start(); mAnimationDrawable.stop();
补间动画
在Android动画中,补间动画一共可以分成四类即透明度动画、缩放动画、旋转动画、位移动画。
其实现方法可以通过xml来配置,也可以通过代码来实现。
透明度动画-AlphaAnimation,缩放动画-ScaleAnimation,位移动画-TranslateAnimation,旋转动画-RotateAnimation
通过xml方式实现
用xml
实现补间动画,需要将xml
放到res下的anim
目录,Android工程默认是没有anim
文件夹的在读文件前我们先把anim
文件夹以及文件建好
点中工程的res
目录 右键New
->Directory
-> 弹窗中输入anim
点中刚刚新建的anim
目录 右键New
-> Animation Resource File
创建好了以后输入如下的布局文件代码
透明度
1 2 3 4 5 6 <?xml version="1.0" encoding="utf-8" ?> <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:fromAlpha="1.0" android:toAlpha="0.1" android:duration="2000" />
注:android:interpolator=""
Interpolator
分类如下:
AccelerateDecelerateInterpolator
,效果是开始和结束的速率比较慢,中间加速;
AccelerateInterpolator
,效果是开始速率比较慢,后面加速;
DecelerateInterpolator
,效果是开始速率比较快,后面减速;
LinearInterpolator
,效果是速率是恒定的;
AnticipateInterpolator
,效果是开始向后甩,然后向前;
AnticipateOvershootInterpolator
,效果是开始向后甩,冲到目标值,最后又回到最终值;
OvershootInterpolator
,效果开始向前甩,冲到目标值,最后又回到了最终值;
BounceInterpolator
,效果是在结束时反弹;
CycleInterpolator
,效果是循环播放,速率是正弦曲线;
TimeInterpolator
,一个接口,可以自定义插值器。
插值器们对应的资源ID:
AccelerateDecelerateInterpolator
,对应的是@android:anim/accelerate_decelerate_interpolator
;
AccelerateInterpolator
,对应的是@android:anim/accelerate_interpolator
;
DecelerateInterpolator
,对应的是@android:anim/decelerate_interpolator
;
LinearInterpolator
,对应的是@android:anim/linear_interpolator
;
AnticipateInterpolator
,对应是@android:anim/anticipate_interpolator
;
AnticipateOvershootInterpolator
,对应的是@android:anim/anticipate_overshoot_interpolator
;
OvershootInterpolator
,对应的是@android:anim/overshoot_interpolator
;
BounceInterpolator
,对应是@android:anim/bounce_interpolator
;
CycleInterpolator
,对应是@android:anim/cycle_interpolator
。
属性值
含义
fromAlpha
起始透明度(透明度的范围为:0-1,完全透明-完全不透明)
toAlpha
结束透明度
duration
持续时间(毫秒)
缩放
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="utf-8" ?> <scale xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator" android:fromXScale="0.2" android:toXScale="1.5" android:fromYScale="0.2" android:toYScale="1.5" android:pivotX="50%" android:pivotY="50%" android:duration="2000" />
属性值
含义
fromXScale
沿着X轴缩放的起始比例
fromYScale
沿着Y轴缩放的起始比例
toXScale
沿着X轴缩放的结束比例
toYScale
沿着Y轴缩放的结束比例
pivotX
缩放的中轴点X坐标,即距离自身左边缘的位置,比如50%就是以图像的 中心为中轴点
pivotY
缩放的中轴点Y坐标
duration
持续时间
位移
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="utf-8" ?> <translate xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:fromXDelta="0" android:toXDelta="320" android:fromYDelta="0" android:toYDelta="0" android:duration="2000" />
属性值
含义
fromXDelta
动画起始位置的X坐标
fromYDelta
动画起始位置的Y坐标
toXDelta
动画结束位置的X坐标
toYDelta
动画结束位置的Y坐标
duration
持续时间
旋转
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="utf-8" ?> <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:fromDegrees="0" android:toDegrees="360" android:duration="1000" android:repeatCount="1" android:repeatMode="reverse" />
属性值
含义
fromDegrees/toDegrees
旋转的起始/结束角度
repeatCount
旋转的次数,默认值为0,代表一次,假如是其他值,比如3,则旋转4次 另外,值为-1或者infinite时,表示动画永不停止
repeatMode
设置重复模式,默认restart,但只有当repeatCount大于0或者infinite或-1时 才有效。还可以设置成reverse,表示偶数次显示动画时会做方向相反的运动
duration
持续时间
组合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?xml version="1.0" encoding="utf-8" ?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/decelerate_interpolator" android:shareInterpolator="true" > <scale android:duration="2000" android:fromXScale="0.2" android:fromYScale="0.2" android:pivotX="50%" android:pivotY="50%" android:toXScale="1.5" android:toYScale="1.5" /> <rotate android:duration="1000" android:fromDegrees="0" android:repeatCount="1" android:repeatMode="reverse" android:toDegrees="360" /> <translate android:duration="2000" android:fromXDelta="0" android:fromYDelta="0" android:toXDelta="320" android:toYDelta="0" /> <alpha android:duration="2000" android:fromAlpha="1.0" android:toAlpha="0.1" /> </set>
通过Java代码实现
透明度
1 2 3 4 public AlphaAnimation (float fromAlpha, float toAlpha) { }
1 2 3 animation= new AlphaAnimation (0 , 1 ); animation.setDuration(2000 ); mImageView.startAnimation(animation);
缩放
1 2 3 4 public ScaleAnimation (float fromX, float toX, float fromY, float toY, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) { }
1 2 3 4 5 animation = new ScaleAnimation (0 , 1.4f , 0 , 1.4f , ScaleAnimation.RELATIVE_TO_SELF,0.5f ,ScaleAnimation.RELATIVE_TO_SELF,0.5f ); animation.setDuration(2000 ); mImageView.startAnimation(animation);
重要:
属性值
含义
pivotXType
有2种模式,RELATIVE_TO_SELF(相对于自身)和RELATIVE_TO_PARENT(相对于父布局)pivotx,pivotY的值就应该是0-1的浮点数,分别对应xml中的%(自身)和%p(父布局)
pivotYType
同pivotXType
mRotateAnimation.setFillAfter(true);//旋转完后停止
1 view.requestLayout();//重新计算布局高度,布局修改时diao
位移
x正向右(小变化到大),y正向下(小变化到大)
1 2 3 4 5 6 7 8 public TranslateAnimation (int fromXType, float fromXValue, int toXType, float toXValue,int fromYType, float fromYValue, int toYType, float toYValue) { }
1 2 3 4 animation= new TranslateAnimation (TranslateAnimation.RELATIVE_TO_SELF, 0 , TranslateAnimation.RELATIVE_TO_SELF, 0.5f , TranslateAnimation.RELATIVE_TO_SELF, 0 , TranslateAnimation.RELATIVE_TO_SELF, 0.5f ); animation.setDuration(2000 ); mImageView.startAnimation(animation);
旋转
1 2 3 public RotateAnimation (float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) { }
1 2 3 4 animation= new RotateAnimation (0 , -720 , RotateAnimation.RELATIVE_TO_SELF, 0.5f , RotateAnimation.RELATIVE_TO_SELF, 0.5f ); animation.setDuration(2000 ); mImageView.startAnimation(animation);
组合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Animation rotateAnimation = new RotateAnimation (0 , -720 , RotateAnimation.RELATIVE_TO_SELF, 0.5f , RotateAnimation.RELATIVE_TO_SELF, 0.5f ); rotateAnimation.setDuration(2000 ); Animation translateAnimation = new TranslateAnimation (TranslateAnimation.RELATIVE_TO_PARENT, 0 , TranslateAnimation.RELATIVE_TO_PARENT, 0.5f , TranslateAnimation.RELATIVE_TO_PARENT, 0 , TranslateAnimation.RELATIVE_TO_PARENT, 0.5f ); translateAnimation.setDuration(2000 ); Animation scaleAnimation = new ScaleAnimation (0 , 1.4f , 0 , 1.4f , ScaleAnimation.RELATIVE_TO_SELF, 0.5f , ScaleAnimation.RELATIVE_TO_SELF, 0.5f ); scaleAnimation.setDuration(2000 ); Animation alphaAnimation = new AlphaAnimation (0 , 1 );alphaAnimation.setDuration(2000 ); AnimationSet animationSet = new AnimationSet (true );animationSet.addAnimation(rotateAnimation); animationSet.addAnimation(translateAnimation); animationSet.addAnimation(scaleAnimation); animationSet.addAnimation(alphaAnimation); animationSet.setDuration(4000 ); animationSet.setFillAfter(true ); mImageView.startAnimation(animationSet);
属性动画(补间动画优化版)
补间动画作用的对象是View,也就是作用的对象是Android中的控件,如ImageView、Button、TextView等,也可以作用在布局上如LinearLayout、ConstraintLayout、RelativeLayout等,但是对于一些不是View的对象,无法对这些对象进行动画操作。比如我们要对某个控件的某个属性做进行动画操作,如其颜色,这个颜色也可以看成一个对象,但其并不是View对象,补间动画就无法实现,属性动画可以对这个颜色值做动画, 能实现一些更加复杂的动画效果。
补间动画只是改变了View
的视觉效果,而不会真正去改变View的属性
ObjectAnimator 对象动画 ValueAnimator的子类,允许对指定对象的属性执行动画。
ValueAnimator 值动画 计算初始值和结束值的过渡动画。
PropertyValueHolder 用于同时执行多个动画
TypeEvaluator 估值器
AnimatorSet 动画集合 Animator的子类,用于组合多个Animator,制定多个动画的播放次序。
Interpolator 差值器
1、属性动画都是通过ValueAnimator 类和ObjectAnimator 类来完成,其中ObjectAnimator类是对对象做动画,ValueAnimator 类是对值做动画。 2、PropertyValueHolder类可以同时执行多个动画,AnimatorSetl类可以将多个动画按一定的秩序先后执行。 3、TypeEvaluator估值器和Interpolator 差值器
对象动画(ObjectAnimator)
ObjectAnimator
类是属性动画中非常重要的一个类,可以通过该类对View
不仅可以实现一些基本的移、旋转、缩放和透明度四种基本变换动画,还能实现一些其他属性值的变换动画。
Java方法实现
1 2 3 4 5 6 7 8 public static ObjectAnimator ofFloat (Object target, String propertyName, float ... values) { ObjectAnimator anim = new ObjectAnimator (target, propertyName); anim.setFloatValues(values); return anim; }
属性
值的用法
rotation
以屏幕方向为轴的旋转度数
alpha
透明度
translationX / translationY
X/Y方向的位移
scaleX /scaleY
X/Y方向的缩放倍数
rotationX / rotationY
以X/Y轴为轴的旋转度数
具体使用
1 2 3 4 ImageView imageView = findViewById(R.id.imageView);ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha" , 1f , 0f , 1f );animator.setDuration(5000 ); animator.start();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ImageView imageView = findViewById(R.id.imageView);ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha" , 1f , 0f , 1f );animator.setDuration(2000 ); animator.setStartDelay(500 ); animator.setRepeatCount(3 ); animator.setRepeatMode(ValueAnimator.RESTART); animator.addUpdateListener(new ValueAnimator .AnimatorUpdateListener() { @Override public void onAnimationUpdate (ValueAnimator animation) { Log.i("MainActivity" ,"value:" +animation.getAnimatedValue()); } }); animator.start();
xml方式实现
在res
目录下新建animator
文件夹animator
文件夹下创建动画XML文件,如animator_alpha.xml
往该xml文件中输入如下代码
1 2 3 4 5 6 <?xml version="1.0" encoding="utf-8" ?> <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:propertyName="alpha" android:valueFrom="1" android:valueTo="0" android:valueType="floatType" />
1 2 3 4 ImageView imageView = findViewById(R.id.imageView);Animator animator = AnimatorInflater.loadAnimator(Main2Activity.this , R.animator.animator_alpha);animator.setTarget(imageView); animator.start();
值动画(ValueAnimator)
1 2 3 ValueAnimator ofFloat (float ... values) -- 浮点型数值 ValueAnimator ofInt (int ... values) -- 整型数值 ValueAnimator ofObject (TypeEvaluator evaluator, Object... values) -- 自定义对象类型
1 2 3 4 5 6 7 8 9 10 11 12 final ImageView imageView = findViewById(R.id.imageView);ValueAnimator anim = ValueAnimator.ofFloat(0f , 1f );anim.setDuration(5000 ); anim.addUpdateListener(new ValueAnimator .AnimatorUpdateListener() { @Override public void onAnimationUpdate (ValueAnimator animation) { float currentValue = (float ) animation.getAnimatedValue(); Log.d("MainActivity" , "cuurent value is " + currentValue); imageView.setAlpha(currentValue); } }); anim.start();
PropertyValueHolder
PropertyValueHolder
可以让前面的一些动画同时执行。
1 2 3 4 5 6 7 8 9 10 11 ImageView imageView = findViewById(R.id.imageView);PropertyValuesHolder alphaProper = PropertyValuesHolder.ofFloat("alpha" , 0.5f , 1f );PropertyValuesHolder scaleXProper = PropertyValuesHolder.ofFloat("scaleX" , 0f , 1f );PropertyValuesHolder scaleYProper = PropertyValuesHolder.ofFloat("scaleY" , 0f , 1f );PropertyValuesHolder translationXProper = PropertyValuesHolder.ofFloat("translationX" , -100 , 100 );PropertyValuesHolder translationYProper = PropertyValuesHolder.ofFloat("translationY" , -100 , 100 );PropertyValuesHolder rotationProper = PropertyValuesHolder.ofFloat("rotation" , 0 , 360 );ValueAnimator animator = ObjectAnimator.ofPropertyValuesHolder(imageView, alphaProper, scaleXProper, scaleYProper,translationXProper,translationYProper,rotationProper); animator.setDuration(5000 ); animator.start();
动画组合(AnimatorSet)
前面的PropertyValueHolder类能实现将多个动画同时执行,AnimatorSet类不仅能让多个动画同时执行,还能让多个动画按一定的顺序执行,同时也能穿插多个动画同时执行。 主要的方法如下:
after(Animator anim)将现有动画插入到传入的动画之后执行 after(long delay) 将现有动画延迟指定毫秒后执行 before(Animator anim) 将现有动画插入到传入的动画之前执行 with(Animator anim) 将现有动画和传入的动画同时执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ImageView imageView = findViewById(R.id.imageView);ObjectAnimator rotate = ObjectAnimator.ofFloat(imageView, "rotation" , 0f , 360f );ObjectAnimator translationX = ObjectAnimator.ofFloat(imageView, "translationX" , -100 , 100f );ObjectAnimator translationY = ObjectAnimator.ofFloat(imageView, "translationY" , -100 , 100f );ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX" , 0 , 1f );ObjectAnimator scaleY = ObjectAnimator.ofFloat(imageView, "scaleY" , 0 , 1f );ObjectAnimator alpha = ObjectAnimator.ofFloat(imageView, "alpha" , 1f , 0f , 1f );AnimatorSet animSet = new AnimatorSet ();animSet.play(rotate) .with(alpha) .after(scaleX) .before(translationX) .after(1000 ) .before(translationY) .with(scaleY); animSet.setDuration(5000 ); animSet.start();
差值器(Interpolator)
前面的动画属性的变换都是均匀变换,可以通过差值器(Interpolator)
来控制值变化的速率
1 2 3 4 5 6 ImageView imageView = findViewById(R.id.imageView);ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha" , 0f , 1f );animator.setDuration(5000 ); animator.setInterpolator(new AccelerateInterpolator (5 )); animator.start();
系统提供,也可自定义
动画名称
效果
AccelerateInterpolator
加速查值器,参数越大,速度越来越快
DecelerateInterpolator
减速差值起,和加速查值器相反
AccelerateDecelerateInterpolator
先加速后减速
AnticipateInterpolator
先后退在加速前进
AnticipateOvershootInterpolator
以X/Y轴为轴的旋转度数
BounceInterpolator
弹球效果插值
CycleInterpolator
周期运动插值
LinearInterpolator
匀速插值
OvershootInterpolator
先快速完成动画,再回到结束样式
估值器(TypeEvaluator) 在前面的值动画(ValueAnimator)中和对象动画(ObjectAnimator)有一个传对象的方法:
1 2 3 ValueAnimator ofObject (TypeEvaluator evaluator, Object... values) ObjectAnimator ofObject (Object target, String propertyName, TypeEvaluator evaluator, Object... values)
这些方法动都需要传一个TypeEvaluator,我们先来看下这个类的源码
1 2 3 public interface TypeEvaluator <T> {public T evaluate (float fraction, T startValue, T endValue) ;}
从TypeEvaluator估值器的源码可以看出该类的作用就是告诉动画,如何从起始值过度到结束值。 Android源码中有好几个类实现来该接口,也就是系统提供的一些默认估值器, 我们以FloatEvaluator为例看下其实现代码。
1 2 3 4 5 6 public class FloatEvaluator implements TypeEvaluator <Number> { public Float evaluate (float fraction, Number startValue, Number endValue) { float startFloat = startValue.floatValue(); return startFloat + fraction * (endValue.floatValue() - startFloat); } }
从FloatEvaluator的实现可以看出在evaluate方法中用结束值减去初始值,算出它们之间的差值,然后乘以fraction这个系数,再加上初始值,那么就得到当前动画的值了
我们也可以以该方法为例 实现一个自定义的估值器实现一个背景颜色值的变化
我们先定义一个默认估值器类MyTypeEvaluator,该类自定义了颜色过渡的方式
package com.lucashu.animation; import android.animation.TypeEvaluator; public class MyTypeEvaluator implements TypeEvaluator { private int mCurrentRed = -1; private int mCurrentGreen = -1; private int mCurrentBlue = -1; @Override public String evaluate(float fraction, String startValue, String endValue) { int startRed = Integer.parseInt(startValue.substring(1, 3), 16); int startGreen = Integer.parseInt(startValue.substring(3, 5), 16); int startBlue = Integer.parseInt(startValue.substring(5, 7), 16); int endRed = Integer.parseInt(endValue.substring(1, 3), 16); int endGreen = Integer.parseInt(endValue.substring(3, 5), 16); int endBlue = Integer.parseInt(endValue.substring(5, 7), 16); // 初始化颜色的值 if (mCurrentRed == -1) { mCurrentRed = startRed; } if (mCurrentGreen == -1) { mCurrentGreen = startGreen; } if (mCurrentBlue == -1) { mCurrentBlue = startBlue; } // 计算初始颜色和结束颜色之间的差值 int redDiff = Math.abs(startRed - endRed); int greenDiff = Math.abs(startGreen - endGreen); int blueDiff = Math.abs(startBlue - endBlue); int colorDiff = redDiff + greenDiff + blueDiff; if (mCurrentRed != endRed) { mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0, fraction); } else if (mCurrentGreen != endGreen) { mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff, redDiff, fraction); } else if (mCurrentBlue != endBlue) { mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff, redDiff + greenDiff, fraction); } // 将计算出的当前颜色的值组装返回 String currentColor = “#” + getHexString(mCurrentRed) + getHexString(mCurrentGreen) + getHexString(mCurrentBlue); return currentColor; } /** * 根据fraction值来计算当前的颜色。 / private int getCurrentColor(int startColor, int endColor, int colorDiff, int offset, float fraction) { int currentColor; if (startColor > endColor) { currentColor = (int) (startColor - (fraction * colorDiff - offset)); if (currentColor < endColor) { currentColor = endColor; } } else { currentColor = (int) (startColor + (fraction * colorDiff - offset)); if (currentColor > endColor) { currentColor = endColor; } } return currentColor; } / * * 将10进制颜色值转换成16进制。 */ private String getHexString(int value) { String hexString = Integer.toHexString(value); if (hexString.length() == 1) { hexString = “0” + hexString; } return hexString; } }
再自定义一个View,在该类中画一个矩形框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class MyView extends View {private String color;private Paint mPaint;public MyView (Context context, AttributeSet attrs) { super (context, attrs); mPaint = new Paint (Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.WHITE); } public String getColor () { return color; } public void setColor (String color) { this .color = color; mPaint.setColor(Color.parseColor(color)); invalidate(); } @Override protected void onDraw (Canvas canvas) { super .onDraw(canvas); canvas.drawRect(0 , 0 , 500 ,500 , mPaint); } }
自顶一个View在布局文件中添加如下
1 2 3 4 5 6 7 8 <com.lucashu.animation.MyView android:id="@+id/myview" android:layout_width="200dp" android:layout_height="200dp" android:layout_marginBottom="100dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" />
将在估值器加到动画中,该动画作用在我们自定义的View上
1 2 3 4 5 6 MyView imageView = findViewById(R.id.myview);ObjectAnimator anim = ObjectAnimator.ofObject( imageView,"color" , new MyTypeEvaluator (), "#0000FF" ,"#FF0000" ); anim.setDuration(5000 ); anim.start();
Handler和Runable 启动页 安卓启动app会自带一个页面,这个页面会加载app,之后才会加载自己设置的启动页面,如果启动页面的逻辑很快过去就会显得欢迎页面一闪而过切有滑动显示自带启动页情况
1 2 3 4 5 6 7 8 9 Runnable runnable = new Runnable () { @Override public void run () { startActivity(new Intent (WelcomeActivity.this , MainActivity.class)); } }; Handler handler = new Handler (); handler.postDelayed(runnable,2000 );
冷启动背景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <style name ="AppTheme.Launcher" parent ="Theme.AppCompat.Light.NoActionBar" > <item name ="android:windowBackground" > @drawable/bg_splash</item > </style > <activity android:name =".ui.activity.SplashActivity" android:exported ="true" android:theme ="@style/AppTheme.Launcher" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity >
Handler消息机制 (28条消息) Android——Handler详解_android handler_Yawn__的博客-CSDN博客
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 mHandler = new Handler (Looper.myLooper()){ @Override public void handleMessage (@NonNull Message msg) { super .handleMessage(msg); if (msg.what==0 ){ Boolean isStart = (Boolean) msg.obj; if (isStart){ startActivity(new Intent (WelcomeActivity.this ,HomeActivity.class)); finish(); } } } }; new Thread (new Runnable () { @Override public void run () { Message message = new Message (); message.what = 0 ; message.arg1 = 0 ; message.arg2 = 1 ; message.obj = true ; mHandler.sendMessage(message); } }).start();
Material Design设计语言 Material Design(中文名:材料设计语言),是由Google推出的设计语言 ,这种设计语言旨在为手机、平板电脑、台式机和“其他平台”提供更一致、更广泛的“外观和感觉” 。
那为什么谷歌会觉得Material Design可以解决Android平台界面风格不统一的问题呢?
一言蔽之——好看。
我们平常建立一个项目,原生的顶上标题栏Actionbar
都是深紫色的标题栏,而且ActionBar
是一个只能位于活动的顶部的标题栏。
ActionBar
弃用了,可以用ToolBar
实现一样的功能
替代ActionBar
主题改成<style name="Theme.MyApplication" parent="Theme.MaterialComponents.DayNight.NoActionBar.Bridge">
,在xml使用Toolbar
控件即可,要显示什么标题在AndroidManifest.xml
设置android:label="Fruits"
1 2 3 4 5 6 7 <androidx.appcompat.widget.Toolbar android:id ="@+id/toolbar" android:layout_width ="match_parent" android:layout_height ="?attr/actionBarSize" android:background ="?attr/colorPrimary" android:theme ="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme ="@style/ThemeOverlay.AppCompat.Light" />
1 2 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);setSupportActionBar(toolbar);
自定义ToolBar
菜单
先在res
下新建menu
资源文件夹,例子如下,item
标签表示一个新建选项,icon
为图标,title
标题,showAction
为显示方式,showAsAction
主要有以下几种值可选:always表示永远显示在 Toolbar
中,如果屏幕空间不够则不显示;ifRoom
表示屏幕空间足够的情况下显示在Toolbar
中,不够的话就显示在菜单当中;never
则表示水远显示在菜单当中。在菜单中的项没有图标,即使设置了图标,只显示文字,菜单为收起形式,点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?xml version="1.0" encoding="utf-8" ?> <menu xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" > <item android:id ="@+id/backup" android:icon ="@drawable/backup" android:title ="Backup" app:showAsAction ="always" /> <item android:id ="@+id/delete" android:icon ="@drawable/delete" android:title ="Backup" app:showAsAction ="ifRoom" /> <item android:id ="@+id/settings" android:icon ="@drawable/settings" android:title ="Backup" app:showAsAction ="never" /> </menu >
然后在Java中重写两个方法来导入menu和使用,在onOptionsItemMenu()
方法中加载了菜单文件,然后onOptionsItemSelected()
方法中处理ActionBar
各个按钮的点击事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Override public boolean onCreateOptionsMenu (Menu menu) { getMenuInflater().inflate(R.menu.toolbar,menu); return true ; } @Override public boolean onOptionsItemSelected (MenuItem item) { switch (item.getItemId()){ case R.id.backup: Toast.makeText(this ,"You clicked Backup " ,Toast.LENGTH_SHORT).show(); break ; case R.id.delete: Toast.makeText(this ,"You clicked Delete " ,Toast.LENGTH_SHORT).show(); break ; case R.id.settings: Toast.makeText(this ,"You clicked Settings " ,Toast.LENGTH_SHORT).show(); break ; default : } return true ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public boolean onMenuOpened (int featureId, Menu menu) { if (menu != null ) { if (menu.getClass().getSimpleName().equalsIgnoreCase("MenuBuilder" )) { try { Method method = menu.getClass().getDeclaredMethod("setOptionalIconsVisible" , Boolean.TYPE); method.setAccessible(true ); method.invoke(menu, true ); } catch (Exception e) { e.printStackTrace(); } } } return super .onMenuOpened(featureId, menu); }
滑动菜单(右滑显示,如QQ) DrawerLayout
(抽屉,常用)这个控件中允许有2个子布局(控件),第一个显示在外面,第二个为折叠起来,右滑展开,其中第二个控件中的layout_gravity
是必须需要指定的,其中left
表示菜单在左边,start
表示会根据系统语言进行判断,如果系统语言是从左到右的,则滑动菜单在左边。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?xml version="1.0" encoding="utf-8" ?> <androidx.drawerlayout.widget.DrawerLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" android:id ="@+id/draw_layout" android:layout_width ="match_parent" android:layout_height ="match_parent" tools:context =".MainActivity" > <FrameLayout android:layout_width ="match_parent" android:layout_height ="match_parent" > <androidx.appcompat.widget.Toolbar android:id ="@+id/toolbar" android:layout_width ="match_parent" android:layout_height ="?attr/actionBarSize" android:background ="?attr/colorPrimary" android:theme ="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme ="@style/ThemeOverlay.AppCompat.Light" /> </FrameLayout > <TextView android:layout_width ="300dp" android:layout_height ="match_parent" android:layout_gravity ="start" android:background ="#FFF" android:text ="This is menu" android:textSize ="30sp" /> </androidx.drawerlayout.widget.DrawerLayout >
为了让隐藏菜单更容易被触发,我们在Toolbar
上加一个导航按钮,点击它也可以打开滑动菜单(防止用户不知道左滑能打开东西,参考QQ设计)。注:HomeAsUp
默认为返回按钮,图标为箭头,这里就把这个按钮替换了图标和功能罢了。 GravityCompat.START
和xml定义一样,xml布局里面在菜单中的子布局android:layout_gravity="start"
属性一样,还有GravityCompat.END
1 2 3 4 5 6 7 8 9 10 11 12 ActionBar actionBar = getSupportActionBar() if (actionBar!=null ){ actionBar.setDisplayHomeAsUpEnabled(true ); actionBar.setHomeAsUpIndicator(R.drawable.ic_menu); } case android.R.id.home: mDrawerLayout.openDrawer(GravityCompat.START); break ;
NavigationView
优化滑动呼出菜单(自带免设计罢了)(包含图片圆形化库)1 implementation'com.google.android.material:material:1.0.0'
1 implementation'de.hdodenhof:circleimageview:3.0.1'
准备两个部分的东西,menu菜单(NavigationView中的选项菜单)和headerLayout(NavigationView中的头部布局)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?xml version="1.0" encoding="utf-8" ?> <menu xmlns:android ="http://schemas.android.com/apk/res/android" > <group android:checkableBehavior ="single" > <item android:id ="@+id/nav_call" android:icon ="@drawable/nav_call" android:title ="Call" /> <item android:id ="@+id/nav_friend" android:icon ="@drawable/nav_friend" android:title ="Friend" /> <item android:id ="@+id/nav_location" android:icon ="@drawable/nav_location" android:title ="Location" /> <item android:id ="@+id/nav_mail" android:icon ="@drawable/nav_mail" android:title ="Mail" /> <item android:id ="@+id/nav_task" android:icon ="@drawable/nav_task" android:title ="Task" /> </group > </menu >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 //headerLayout <?xml version="1.0" encoding="utf-8" ?> <RelativeLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="180dp" android:padding ="10dp" android:background ="?attr/colorPrimary" > <de.hdodenhof.circleimageview.CircleImageView android:id ="@+id/icon_image" android:layout_width ="70dp" android:layout_height ="70dp" android:src ="@drawable/icon" android:layout_centerInParent ="true" /> <TextView android:id ="@+id/username" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_alignParentBottom ="true" android:text ="wendy" android:textColor ="#FFF" android:textSize ="14sp" /> <TextView android:id ="@+id/mail" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_above ="@id/username" android:text ="1111@qq" android:textColor ="#FFF" android:textSize ="14sp" /> </RelativeLayout >
1 2 3 4 <com.google.android.material.navigation.NavigationView android:layout_width ="wrap_content" android:layout_height ="match_parent" android:layout_gravity ="start" />
这里layout_gravity一定要有,不然会阻挡整个屏幕,toolbar会不见掉
1 2 3 4 5 6 7 8 9 10 11 NavigationView navigationView = findViewById(R.id.nav_view); navigationView.setCheckedItem(R.id.nav_call); navigationView.setNavigationItemSelectedListener(new NavigationView .OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected (@NonNull MenuItem item) { mDrawerLayout.closeDrawers(); return true ; } });
1 2 3 4 5 6 7 8 9 nav_home = (NavigationView) findViewById(R.id.nav_home); CardView cardView = nav_home.getHeaderView(0 ).findViewById(R.id.cv_exit); cardView.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { Toast.makeText(HomeActivity.this , "ddd" , Toast.LENGTH_SHORT).show(); } });
悬浮按钮和可交互提示 立面设计是Material Design中的一条重要设计思想,而悬浮按钮就是最具有代表性的立面设计了,这种按钮不属于主界面平面的一部分而是位于另外一个维度。同时我们也学习一种可交互式的提示。
android:elevation="100dp"
设置悬浮高度,下面有阴影
1 2 3 4 5 6 7 8 <com.google.android.material.floatingactionbutton.FloatingActionButton android:id ="@+id/fab" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_gravity ="bottom|end" android:layout_margin ="16dp" android:elevation ="100dp" android:src ="@drawable/ic_done" />
这个控件监听方法同平台button
Snackbar
可交互提示首先明确Snackbar
和Toast
有各自的应用场景,Snackbark
可以给用户提供了一个可交互按钮,给用户提供一些额外操作。基本同Toast
,toast只是为了发出一个提示给用户罢了,有些如删除数据的场景就可以用这种带交互的好,可以允许用户取消删除
1 2 3 4 5 6 7 8 9 10 11 12 floatingActionButton.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { Snackbar.make(v,"toast" ,Snackbar.LENGTH_SHORT) .setAction("Undo" , new View .OnClickListener() { @Override public void onClick (View v) { Toast.makeText(MainActivity.this , "取消" , Toast.LENGTH_SHORT).show(); } }).show(); } });
CoordinatorLayout
布局CoordinatorLayout
可以说是加强版的FrameLayout
(帧布局),也是Design Support库提供的,它在普通情况下和FrameLayout
作用基本一致。不一样的是CoordinatorLayout
可以监听其子控件的各种事件,然后自动帮助我们做出最为合理的响应。
举个例子,刚刚弹出来的Snackbar提示把悬浮按钮遮挡住了,这时让CoordinatorLayout监听到Snackbar的弹出事件,它就会自动让FloatingActionButton向上偏移。
直接在代码中将FrameLayout
替换一下就可以
卡片式布局(包含Glide库) CardView
(类FrameLayout
)CardView是实现卡片上布局的重要控件。它实际上也是一个FrameLayout,只是额外提供了圆角和阴影的效果,看上去会有立体的感觉。
1、cardBackgroundColor 设置背景色
CardView是View的子类,View一般使用Background设置背景色,为什么还要单独提取出一个属性让我们来设置背景色呢?
为了实现阴影效果,内部已经消耗掉了 Background 属性
2、cardCornerRadius 设置圆角半径
3、contentPadding 设置内部padding
View提供了padding设置间距,为什么还要单独提取出一个属性?
相同的原因,内部消耗掉了 padding 属性
4、cardElevation 设置阴影大小
5、cardUseCompatPadding
默认为false,用于5.0及以上,true则添加额外的 padding 绘制阴影
6、cardPreventCornerOverlap
默认为true,用于5.0及以下,添加额外的 padding,防止内容和圆角重叠
Glide 是一个超级强大的图片加载库,可以加载本地图片、网络图片、gif图片,甚至本地视频。而且Glide的用法也很简单,只用一行代码我们会在下面进行设置。
1 2 3 4 implementation'com.github.bumptech.glide:glide:4.9.0'
1 2 3 4 5 Glide.with(context) .load(path.toString()) .error(R.mipmap.menu_cancellation) .diskCacheStrategy(DiskCacheStrategy.RESULT) .into(imageView);
AppBarLayout
解决CoordinatorLayou
默认左上角放置重叠问题运行后我们发现,先前的Toolbar
被遮挡了,这是因为CoordinatorLayou
是和FrameLayout
一样,在没有进行明确定位时,默认都会摆放在布局的左上角,所以产生了遮挡。那除了对RecyclerView
进行偏移,我们还可以借助Design Support库中提供的另外一个工具——AppBarLayout
来解决遮挡。**AppBarLayout
实际上是一个垂直方向的LinearLayout
**,它在内部做了很多滚动事件的封装,并应用了一些Material Design的设计理念。AppBarLayout
又必须是CoordinatorLayout
的子布局
包住冲突事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <com.google.android.material.appbar.AppBarLayout android:layout_width ="match_parent" android:layout_height ="wrap_content" > <androidx.appcompat.widget.Toolbar android:id ="@+id/toolbar" android:layout_width ="match_parent" android:layout_height ="?attr/actionBarSize" android:background ="?attr/colorPrimary" android:theme ="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme ="@style/ThemeOverlay.AppCompat.Light" /> </com.google.android.material.appbar.AppBarLayout >
然后再另外给**RecyclerView
**指定一个布局行为
1 app:layout_behavior="@string/appbar_scrolling_view_behavior"
运行看效果
那现在我们只是用AppBarLayout解决了遮挡问题,那应用了一些Material Design的设计理念是怎么体现的呢?其实这时RecyclerView已经将滚动事件通知了AppBarLayout,但是我们还没有进行处理,我们在Toolbar 添加以下代码:
1 app:layout_scrollFlags="scroll|enterAlways|snap"
scroll
表示当RecyclerView
向上滚动时,Toolbar
会跟着向上滚动并隐藏;enterAlways
表示当RecyclerView
向下滚动时,Toolbar
会跟着向下滚动并重新显示。snap
表示Toolbar
还没有完全隐藏或显示的时候,会根据当前滚动距离,自动选择是隐藏还是显示。运行看一下。
下拉刷新 swiperefreshlayout
1 implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
包在RecyclerView
外面使用,这时候RecyclerView
添加的滚动行为要放到刷新控件中,不然刷新控件不生效,会被挡住
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 mSwipeRefreshLayout = findViewById(R.id.refresh); mSwipeRefreshLayout.setColorSchemeResources(R.color.black); mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout .OnRefreshListener() { @Override public void onRefresh () { new Thread (new Runnable () { @Override public void run () { try { Thread.sleep(2000 ); } catch (InterruptedException e) { throw new RuntimeException (e); } runOnUiThread(new Runnable () { @Override public void run () { initFruits(); adapter.notifyDataSetChanged(); mSwipeRefreshLayout.setRefreshing(false ); } }); } }).start(); } });
可折叠标题栏 顾名思义,CollapsingToolbarLayout
是一个作用于Toolbar
基础之上的布局,被限定只能作为AppBarLayout
的直接子布局来使用,而AppBarLayout
又必须是CoordinatorLayout
的子布局。
充分利用系统状态栏空间
android:fitsSystemWindows="true"
因为我们现在写的是嵌套结构,所以为了实现这种效果,我们需要分别在CoordinatorLayout
、AppBarLayout
和CollapsingToolbarLayout
设置其android:fitsSystemWindows
属性为ture
,就表示该控件ImageView会出现在系统状态栏上。除了这些我们还要将状态栏的颜色去设置成透明色才行。具体怎么实现大家有兴趣的可以去看书里的具体做法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 Observable.create(new ObservableOnSubscribe <String>() { @Override public void subscribe (ObservableEmitter<String> emitter) throws Exception { emitter.onNext("Hello" ); emitter.onComplete(); } }) .map(new Function <String, String>() { @Override public String apply (String s) throws Exception { return s + " RxJava" ; } }) .subscribe(new Observer <String>() { @Override public void onSubscribe (Disposable d) { } @Override public void onNext (String result) { } @Override public void onError (Throwable e) { } @Override public void onComplete () { } });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 package com.derry.rxjavastudy.simple01;import androidx.appcompat.app.AppCompatActivity;import android.app.ProgressDialog;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.ImageView;import com.derry.rxjavastudy.R;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.URL;import io.reactivex.Observable;import io.reactivex.Observer;import io.reactivex.android.schedulers.AndroidSchedulers;import io.reactivex.annotations.NonNull;import io.reactivex.disposables.Disposable;import io.reactivex.functions.Consumer;import io.reactivex.functions.Function;import io.reactivex.schedulers.Schedulers;public class MainActivity extends AppCompatActivity { private final String TAG = MainActivity.class.getSimpleName(); private final static String PATH = "http://pic1.win4000.com/wallpaper/c/53cdd1f7c1f21.jpg" ; private ProgressDialog progressDialog; private ImageView image; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); image = findViewById(R.id.image); } public void showImageAction (View view) { Observable.just(PATH) .map(new Function <String, Bitmap>() { @NonNull @Override public Bitmap apply (@NonNull String path) throws Exception { try { URL url = new URL (path); HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setConnectTimeout(5000 ); int responseCode = httpURLConnection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { InputStream inputStream = httpURLConnection.getInputStream(); Bitmap bitmap = BitmapFactory.decodeStream(inputStream); return bitmap; } } catch (Exception e) { e.printStackTrace(); } return null ; } }) .map(new Function <Bitmap, Bitmap>() { @NonNull @Override public Bitmap apply (@NonNull Bitmap bitmap) throws Exception { Paint paint = new Paint (); paint.setColor(Color.RED); paint.setTextSize(88 ); Bitmap shuiyingBitmap = drawTextToBitmap(bitmap, "韭菜盖饭" , paint, 88 , 88 ); return shuiyingBitmap; } }) .map(new Function <Bitmap, Bitmap>() { @NonNull @Override public Bitmap apply (@NonNull Bitmap bitmap) throws Exception { Log.e(TAG, "什么时候下载了图片 apply: " + System.currentTimeMillis() ); return bitmap; } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( new Observer <Bitmap>() { @Override public void onSubscribe (Disposable d) { progressDialog = new ProgressDialog (MainActivity.this ); progressDialog.setTitle("RXJava Derry run 正在加载中.." ); progressDialog.show(); } @Override public void onNext (Bitmap bitmap) { image.setImageBitmap(bitmap); } @Override public void onError (Throwable e) { } @Override public void onComplete () { if (progressDialog != null ) progressDialog.dismiss(); } }); } private final Bitmap drawTextToBitmap (Bitmap bitmap, String text, Paint paint, int paddingLeft, int paddingTop) { Bitmap.Config bitmapConfig = bitmap.getConfig(); paint.setDither(true ); paint.setFilterBitmap(true ); if (bitmapConfig == null ) { bitmapConfig = Bitmap.Config.ARGB_8888; } bitmap = bitmap.copy(bitmapConfig, true ); Canvas canvas = new Canvas (bitmap); canvas.drawText(text, paddingLeft, paddingTop, paint); return bitmap; } public void action (View view) { String[] strings = {"AAA" , "BBB" , "CCC" }; Observable.fromArray(strings) .subscribe(new Consumer <String>() { @Override public void accept (@NonNull String s) throws Exception { Log.d(TAG, "accept: " + s); } }); } }
RXJAVA从入门到精通-CSDN博客