AAD-21-2-items
Android tech notes
4/16
package com.google.developers.teacup.dataimport androidx.lifecycle.LiveData
import androidx.paging.DataSource
import androidx.room.*
import androidx.sqlite.db.SupportSQLiteQuery/**
* Room data access object for storing and querying teas by various criteria.
* @see Dao
*/
@Dao
interface TeaDao {/**
* Returns a random Tea
*/
@Query("SELECT * FROM tea ORDER BY RANDOM() LIMIT 1")
fun getRandomTea(): Tea/**
* Returns all data in table for Paging
*
* @param query a dynamic SQL query
*/
@RawQuery(observedEntities = [Tea::class])
fun getAll(query: SupportSQLiteQuery): DataSource.Factory<Int, Tea>/**
* Returns a Tea based on the tea name.
*
* @param name of a tea
*/
@Query("SELECT * FROM tea WHERE name == :name")
fun getTea(name: String): LiveData<Tea>/**
* Update tea if its favorite or not.
*
* @param name of a tea
*/
//@Query("UPDATE tea SET favorite = NOT favorite WHERE name == :name")
@Query("UPDATE tea SET isFavorite = NOT isFavorite WHERE name == :name")
fun updateFavorite(name: String)/**
* Find teas by the location, type, and maximum steep time.
*/
@Query("SELECT * FROM tea where origin=:location AND type=:teaType AND steepTimeMs <= :steepTime")
fun searchTeas(location: String, teaType: String, steepTime: Int): DataSource.Factory<Int, Tea>/**
* Find teas by tea type and maximum steep time.
*/
@Query("SELECT * FROM tea where type=:teaType AND steepTimeMs <= :steepTime")
fun searchTeas(teaType: String, steepTime: Int): DataSource.Factory<Int, Tea>/**
* Find tea by maximum steep time.
*/
@Query("SELECT * FROM tea where steepTimeMs <= :steepTime")
fun searchTeas(steepTime: Int): DataSource.Factory<Int, Tea>/**
* Find tea by location.
*/
@Query("SELECT * FROM tea where origin=:location")
fun searchTeas(location: String): DataSource.Factory<Int, Tea>/**
* Find origin locations of teas.
*/
@Query("SELECT DISTINCT origin FROM tea ")
fun getTeaOrigins(): LiveData<List<String>>/**
* Get a list of tea types
*/
@Query("SELECT DISTINCT type from tea")
fun getTeaTypes(): LiveData<List<String>>@Insert
fun insert(vararg tea: Tea)@Delete
fun delete(tea: Tea)
}
3/8
# 3/8
# EditTextPreference 属性配置:summary显示输入值
- 老版本不支持显示输入值,需要一些hack方法
- ''androidx.preference:preference:1.1.1' 之后有 useSimpleSummaryProvider
- ns是 app:useSimpleSummaryProvider
- 最终的效果是 输入值直接占据 summary的为止
- 如果是
3/7
# 3/7
# 异常 @Throws(Exception::class)
-
@Throws(IOException::class)
fun readFile(name: String): String {...}
- 上面的代码会被JVM转换:
String readFile(String name) throws IOException {...}
- 为什么会在很多kotlin的方法里面看到 @Throws呢? 是java和kotlin的互操作。
- 也就是说 该方法很可能被java代码调用
- 当java调用的实际,按照java的异常规范,强制要求被调用者进行处理
- 但并不影响kotlin的代码
- 另一种是,该方法是继承自java的方法,而该java方法有异常签名
- 比如 # viewModel的获取
- private val model: SharedViewModel by activityViewModels()
在fragment之间共享数据,也就是基于一个Activity的viewModel
- val model: MyViewModel by viewModels()
基于单个fragment的viewModel
- val viewModel = ViewModelProvider(this).get(YourViewModel::class.java)说明:
- 首先,通过这个delegate获取的viewModel都是lazy的,
- 而且可以不用cache,直接一个临时变量即可,即取即用
- viewModels()是从 fragment-ktx artifact 中来的,如果没有的话,则只能使用ViewModelProvider 的方式
-监视liveData:
-
viewModel.xLiveData.observe(this, Observer { data ->
// refresh ui
})
- 另一种写法 省略了 Observer
viewModel.xLiveData.observe(this, data -> {
// refresh ui
})- 这个是在主线程中refresh的
- data是解包后的LiveData问题:
- 当我们需要考虑在更多的地方share数据,比如Activity之间,
- 这儿有讨论
- 这儿有一个实现,也就是基于Application的ViewModel(不是ApplicationViewModel),
- 但是话说回来,如果一个数据是基于applcation的,那我们可以在Application下create实例变量,viewModel的意义其实不大了。
3/6
# 3/6# EditText的输入类型:
- numberDecimal 仅仅能输入正小数值
- android:inputType="numberDecimal|numberSigned" 才能输入负数
- 其他还有很多类型: "phone", "textPassword"
- 具体见这儿
2/25
# ViewModel的一种用法--仅仅数据
- ViewModel的数据来源:在我厂的代码里面,ViewModel仅仅是保留数据,
- 而是在外部设置viewModel中LiveData的值,因此ViewModel就变得很简单。
- 也就是不再是presenter的作用,因此不需要要考虑线程和coroutines的问题
- 也就是只有数据,没有方法
- 而另外有presenter负责数据获取
2/24
# Room db
- Room 只是包装了sqlite
- 包含 DataBase, Table(Entity), Query
- 表:
@Entity(tableName = "word_table")
public class Word { }
- DB
@Database(entities = {Word.class}, version = 1, exportSchema = false)
public abstract class WordRoomDatabase extends RoomDatabase
- DAO: 指如何获取DB数据
@Dao
public interface WordDao { }
- DB create的时候需要一个Application的context,因此需要一个AndroidViewModel
- DAO返回的结果可以是 LiveData
- DB 一般是个单例 singleton/object
- 架构:ViewModel -》Repository-》DB
# Intent
显式Intent
- Intent(context, class) 这是启动Activity的常见implicit Intent, 显式Intent
- i.putExtra("name", value) 可以添加额外的参数,可以很多
- 获取的时候: i.getExtras().getString()
- 或者直接是一个 putExtra(bundle)隐式Intent:通过Action
- 包含Action Data 和 category 三个部分
- Action是个字符串,但是系统定义了很多,
- data就是uri,可以通过setData()/getData设置获取
- flag是一个int,setFlags()/getFlags: 这个不常用,类似Activity的启动模式,SingleTask之类
- 也包含extra,是个bundle使用隐式的好处
- 系统可以指定某个Acitivity为默认的,而不是一定要那个
- 注意:extra参数里面很多是固定名字,使用者才能取得到
-
2/23
# 2/23
# notification
- NotificationManager: 这是系统的service,get到
- 每个app,我们需要create NotificationChannel,new即可,设置这个channel的参数,可以理解为分类,也就是说一个app可以弄多个分类
- 每个notification需要一个 NotificationCompat.Builder
- 触发: mNotifyManager.notify(NOTIFICATION_ID, notifyBuilder.build());
- cancel: mNotifyManager.cancel(NOTIFICATION_ID);
- 每个channel有个id(字符串),每个notification有个id(int)
- 对于每个notification,可以添加多个Action, 这些action会排在content的下面
- 总体的显示:
title
content
Action1 Action2 Action3
- 点击Action,则会执行对应的PendingIntent
- 点击非Action的部分,则会打开App# PendingIntent
- 包装了一个Intent
- 现在不执行,当满足某些条件后执行这个Intent
-
val intent = Intent(ACTION_UPDATE_NOTIFICATION);
val pendingIntent = PendingIntent.getBroadcast(this,
NOTIFICATION_ID, updateIntent, PendingIntent.FLAG_ONE_SHOT)# 接收Notification的Action
- 当点击非action部分的时候:默认自动会打开App
- 每一个Action都对应一个PendingIntent
- 通过NotificationReceiver来获取Action
mReceiver = NotificationReceiver();
registerReceiver(mReceiver, IntentFilter(ACTION_UPDATE_NOTIFICATION));public class NotificationReceiver extends BroadcastReceiver {} 重载onReceive方法- 如果App被kill掉了,则不会action很可能不会执行
- 本文中:如果只接受自己App的Notification的action,则不需要在Manifest中注册接收器
- 如果您在清单中声明广播接收器,系统会在广播发出后启动您的应用(如果应用尚未运行)。--- 这儿会被滥用
- 反过来:如果自己的App发和接收notification和Action。如先发notification然后kill掉App,再点击的时候,可能不会启动自己的App
- 这些东西很不好掌握,尤其是涉及到与系统的斗争。
- 我猜想:我们通过service进行数据处理然后post notification,当然是自己同时生产者和消费者。当post notification的时候,如果App是未启动状态则启动。这就是把一个app唤醒了。比如App的timeout
- 或者是 后台线程处理完某个任务之后显示在notification上。问题:
- 在PendingIntent中,当通过BroadcastReceiver/onReceive接收的时候,里面有个PendingIntent的requestCode参数并没有返回来?仅仅有一个Intent参数。当有多个Action的时候,我们只能通过Intent里面的附加参数。
2/17
# 2/17# codelab: Espresso# Espresso setup
- 不需要太多 androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
- androidx: androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
- 以及 defaultConfig: testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"- ActivityTestRule: 添加这个Rule,则每个case之前就会启动Activity
@get:Rule
val activityRule = ActivityTestRule(MainActivity::class.java)# 简单分析:
onView(withText("Hello world!")).check(matches(isDisplayed()))
onView(withId(R.id.button_main)).perform(click());
- onView(): 定位到某个view,withText是匹配函数,还有withId
- check:这内部蕴含着assert,后接matches
- isDisplayed()/isEnabled()
- perform是操作
- click(): 点击
- typeText("xx"): 输入,针对TextEdit#
- 当执行操作切换了Activity的时候,自然就切换到另外一个,而且正常的切换也无需等待. mActivityRule.getActivity() 这个值并不一定是当前的Activity#
- Spinner: onData, 处理List,就能实现自动scroll功能,其实是处理了AdapterView
- RecyclerView有自己的scrollTo等一系列方法#
- Espresso:可以通过AS录制Espresso的测试,然后查看代码,但是这些代码通常比较繁琐
- Run > Record Espresso Test
- 这个功能很耗CPU#
- ViewInteraction: 找到的View都是这个类型
- 例子里面的搜索方法: childAtPosition()看上去很繁琐,而且很奇怪,比如有个数字13,RecyclerView为什么能确定这个13呢,而且居然跑对了。我把这方法去掉之后还是可以的。看到一屏正好是13个,可能如此。而且这个方法是录制的时候提供的。
- ViewInteraction textView = onView(allOf(withId(R.id.word), withText("+ Word 20"), isDisplayed())); 也能scroll
- 这是另外一种方法:
onView(withId(R.id.recyclerview)).perform(RecyclerViewActions.scrollTo(hasDescendant(withText("+ Word 20"))));# summary
- instrumentTest都是 @RunWith(AndroidJUnit4.class)
- test一般无需RunWith
- @Rule,尤其是ActivityTestRule
- onData 用在动态pop出来的UI上,目前知道的是Spinner
- ViewInteraction 搜索到的View在测试中的类型
- Codelab结束的时候列出了很多资源
-# 问题
- Fragment呢?有没有Rule,我记得B曾说过有类似的
- 需要scroll的
- Button和TextView,EditText是最常见的,但还有其他呢
-
2/16
# 2/16
# AAD test
- androidTest是跑在emulator或者devie上,而test只需local即可
- 三种设备:真机 仿真器 以及 Robolectric,后者仅仅是实现了API
- When creating tests, you have the option of creating real objects or test doubles, such as fake objects or mock objects.
- mock是测试里面常用的,test doubles测试替身
- The Testing Pyramid,金字塔测试模型,UT/integration test/UI test
- 70 percent small, 20 percent medium, and 10 percent large.
- 这是两种不同的分类方法,但基本上是对应的,也就是说 small test基本上是ut
- Robolectric simulates the runtime for Android 4.1 (API level 16) or higher and provides community-maintained fakes called shadows.
- Junit/Hamcrest 以及Guava 团队提供了一个名为 Truth 的流利断言库
- UI Automator API
- smock test在我厂里面指的就是 automation,但smocktest本身指的是没有规定case的随意测试
-# test codelab summary
- Local unit tests use the JVM of your local machine. They don't use the Android framework.
- Unit tests are written with JUnit, a common unit testing framework for Java.
- JUnit tests are located in the (test) folder in the Android Studio Project > Android pane.
- Local unit tests only need these packages: org.junit, org.hamcrest, and android.test.
- The @RunWith(JUnit4.class) annotation tells the test runner to run tests in this class. 作用在class上
- @SmallTest, @MediumTest, and @LargeTest annotations are conventions that make it easier to bundle similar groups of tests
也是作用在class上
- The @SmallTest annotation indicates all the tests in a class are unit tests that have no dependencies and run in milliseconds. 毫秒级别
- Instrumented tests are tests that run on an Android-powered device or emulator. Instrumented tests have access to the Android framework.
- A test runner is a library or set of tools that enables testing to occur and the results to be printed to the log. 通常我们会选择一个Test Runner, 也就是说可以自己实现一个
- source sets: src/, test/, androidTest/# AAD问题:
- 怎么区分哪些才是androidTest
-# JUnit4.12 vs JUnit新版本:异常
- @Test(expected = IndexOutOfBoundsException.class)
- 使用Rule
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testFoo() {
thrown.expect(IndexOutOfBoundsException.class)
thrown.expectMessage("expected messages");
foo.doStuff();
}
- JUnit5
@Test
public void testFooThrowsIndexOutOfBoundsException() {
Throwable exception = assertThrows(IndexOutOfBoundsException.class, () -> foo.doStuff());
assertEquals("expected messages", exception.getMessage());
}# java 除0
- 除数是浮点的话,JVM不会抛出异常,
- 0/0: 输出:NaN。 not a number
- 1d/0: 而是无穷大(Double.POSITIVE_INFINITY) 输出: Infinity
- 如果是整数除法,除数和被除数都不能是浮点,则会抛出ArithmeticException
- 0^0: JVM答案是1而不是0. wiki说这个值目前没有得到统一,大多数认为1,但也有定义为NaN# codelab: Android test
-
2.13
# ViewPager2
- ViewPager2 supports vertical paging in addition to traditional horizontal paging 支持vertical,android:orientation="vertical" 可以设置方向
- ViewPager2 supports right-to-left (RTL) paging. android:layoutDirection
- ViewPager2 is built on RecyclerView# ViewPager adapter
- When ViewPager used PagerAdapter to page through views, use RecyclerView.Adapter with ViewPager2.
- When ViewPager used FragmentPagerAdapter to page through a small, fixed number of fragments, use FragmentStateAdapter with ViewPager2.
- When ViewPager used FragmentStatePagerAdapter to page through a large or unknown number of fragment, use FragmentStateAdapter with ViewPager2.# ViewPager(不是ViewPager2)的adapter
- FragmentPagerAdapter - Use this when navigating between a fixed, small number of sibling screens.
- FragmentStatePagerAdapter - Use this when paging across an unknown number of pages. FragmentStatePagerAdapter optimizes memory usage by destroying fragments as the user navigates away.
- 固定fragment的时候使用前者,动态的时候使用后者#
- TextView在Material下居然app:backgroundTint不好用,android:backgroud好用,真是奇怪,一致性好难
- get screen width/height:
- 更简单的使用 resources.displayMetrics.heightPixels
- 这个不包含toolbar#
- navigation: 居然发现通过nav前进后退回其fragment是cache了的,真是出乎意料
- 但只是针对back的才有效果,navigate前进会create新的instance
- 在side navigation里面,每次点击menu都是重新create的
- # DialogFrament
- guide
- DialogFragment
- BottomSheetDialog
- BottomSheetDialogFragment
- AlertDialog: A subclass of Dialog that can display one, two or three buttons. 相当于iOS上标准的AlertViewController
- Dialog & dialogs: DatePickerDialog, ProgressDialog, TimePickerDialog
- 使用DialogFragment的时候,设置 setGravity(Gravity.BOTTOM)
即就是Bottom两者之间的差别就这么多?
- dialog show/dismiss
if (sheet.isShowing()){
sheet.dismiss();
} else {
sheet.show();
}
- 看到还有一个 cancel的listener,能猜到是当条件合适的时候自己dismiss,但还不知道怎么使用。
- Note: To implement non-modal Persistent bottom sheets use BottomSheetBehavior in conjunction with a CoordinatorLayout. 如果想实现一个非modal的# BottomSheetDialogFragment expand
- 1-使用NestedScrollView
- 2-设置behavior_peekHeight: 也就是弹出的高度 或者 通过BottomSheetBehavior
- BottomSheetBehavior.from(bottomSheet).peekHeight = xx
- # 如何使用 BottomSheetBehavior
- 直接设置: app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
- SDK要求使用CoordinatorLayout,但也不一定
# padding vs margin
- padding属于widget内部属性,比如TextView的padding是 文字显示与边缘的距离
- margin是widget边缘到parent的距离
- 一个影响是 背景颜色,两者背景区域是不一样的
- 还有就是选择的click区域
2.10
# ViewPager + TabLayout
- TabLayout本身只包含头
- 需要和ViewPager配合使用
- 在layout中,两者是对等关系,也就是在同一层次
- ## ViewPager2
- FragmentStateAdapter
- androidx.viewpager2.widget.ViewPager2
- TabLayoutMediator(it.tab, it.vp) { }## ViewPager
- androidx.viewpager.widget.ViewPager
- FragmentStatePagerAdapter
- tab.setupWithViewPager(vp)# navigation
## 进出动画
- app:enterAnim="@anim/slide_in_bottom" 进入的时候的动画
- app:exitAnim="@anim/keep" 进入时候,下面View的退出动画
- app:popExitAnim="@anim/slide_out_bottom" 退出动画
- app:popEnterAnim="@anim/keep" 退出的时候 返回View的进入动画##
- anim是res下的一个资源组
- values/anim 是常量资源,其实可以放在任意 values/文件下,最终都会组合起来,不同的名字只是用于区别而已
- 例子
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0%" android:toXDelta="0%"
android:fromYDelta="0%" android:toYDelta="0%"
android:duration="@integer/slide"/>
</set>## 常见动画
- translate: fromXDelta
- alpha: fromAlpha/toAlpha
##
- 不过我觉得,没有必要去模拟iOS的push和present,完全没有必要
- 比如如果我们使用navigation,则fragment使用同一个toolbar,而这个bar并不在动画范围内,我不确定使用Activity能否看到push/present的效果。
- 我厂的app只设置了进入的动画(fade-in/out),而且退出没有动画--这是个好选择
- 推特的 左右进入 和 上下进入 其实做得很不错,貌似微信也使用类似的动画。但看上去他们都是基于Activity的导航,而不是Navigation和Fragment,可以具体再看看
- 而且back button也没有必要搞成X,考虑到Android的返回键# 子项目的navigation文件
- 可以通过include的方式包含到主nav graph里面,毕竟UI层的依赖基本都是源代码的
- 而且我严重怀疑通过lib 进来的graph文件也可以include进来
- 但是反向就不行了,毕竟在某一个具体的页面要跳转到另一个feature里面,可行的一个方法是通过app中转方法的方式(即通过config来传递一个navigator,也就是closure)
- 也就是说feature之间的跳转不能在Fragment里面添加action直接跳转
2.9
# navigation: safe args 在navigation中使用安全参数
## 不使用安全参数
- 使用Bundle
- val bundle = bundleOf("amount" to 123)
- view.findNavController().navigate(R.id.confirmationAction, bundle)
- 在接收端:arguments?.getString("amount")## 使用安全参数: action方式
- 项目gradle: classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
- plugin: id 'androidx.navigation.safeargs.kotlin'
- 在navigation文件里面的目的fragment下面添加 argument
- AS/plugin 帮助生成两中东西,一个是XXDirections,另一种是XXArgs
## 例子
- 比如我在目标HomeFragment下面添加一个action,跳转到ArticleFragment
- 然后在ArticleFragment下添加参数label
- 则AS/plugin会给我生成: HomeFragmentDirections
- val action = HomeFragmentDirections.navArticle("xx")
- findNavController()?.navigate(action)
- 在navigation中,还可以直接使用args: android:label="{label}"
- 在目标处取出参数:val args: ArticleFragmentArgs by navArgs()
- args.label
##
- safe args可以定义在Fragment下或者是action下,但是定义在action下的无法通过标准的方式获取,也就是说定义在Fragment下的会自动生成一个XXArgs,但这个类不包含action里面的参数
- Navigation里面不仅仅是fragment可以,activity以及dialog都可以##
- default: android:defaultValue="xx"
- optional: app:nullable="true"## action vs fragment 我们可以使用action还是fragment?
- findNavController()?.navigate(xx) 通过这个可以直接调用action和fragment
- action可以设置进出动画,包括从左右以及上下(模拟iOS push/present)
- 所以标准的方式是通过action,而不是直接fragment## 全局action 还是 fragment下的action?
- You can use a global action to create a common action that multiple destinations can use. For example, you might want buttons in different destinations to navigate to the same main app screen.
- 不能引用其他Fragment下的action,否则会crash
- ## navigate
- findNavController()?.navigate(action)
- findNavController().navigate(R.id.xx) // action or fragment
- NavHostFragment.findNavController(this@MsgFragment).navigate(action)
2.8
# theme
# 你使用了哪个theme?
- 在AS4.1.1上,从新create的wizard项目都是"Theme.MaterialComponents.DayNight.DarkActionBar"
- 这个导致一个问题,Button是MaterialButton
- 而MaterialButton的style设置方式与AppCompatButton不一样
- 一般的老项目的theme是based on "Theme.AppCompat.Light.NoActionBar"
- 其实不仅仅是Button,其他widget都有对应的一份
- 而且我估计可能连Toolbar
- MaterialButton ignores android:background. 不再使用背景而是tint
- 完整的见这儿# MaterialButton
- Do not use the android:background attribute
- For filled buttons, this class uses your theme's ?attr/colorPrimary for the background tint color and ?attr/colorOnPrimary for the text color. For unfilled buttons, this class uses ?attr/colorPrimary for the text color and transparent for the background tint. 默认背景色和前景色
- Specify background tint using the app:backgroundTint and app:backgroundTintMode attributes, which accepts either a color or a color state list.
- 看上去 MaterialButton使用的都是app而不是android, 注意AS可能不会自动补齐app。
- Set the stroke color using the app:strokeColor attribute, which accepts either a color or a color state list. Stroke width can be set using the app:strokeWidth attribute. 画笔颜色和宽度
- Specify the radius of all four corners of the button using the app:cornerRadius attribute. 圆角# MaterialButton
- 默认背景使用主题中的colorPrimary,前景使用colorOnPrimary
- 使用 android:textColor 居然还能修改文本的颜色
- 如果 app:backgroundTint=@null, 则背景是纯黑色
- app:backgroundTint="@android:color/transparent" 背景是全透明,但有阴影
配合 android:stateListAnimator="@null" 则可以去掉shadow
- style="?android:attr/borderlessButtonStyle" 没有背景也没有阴影
- or style="@android:style/Widget.Material.Button.Borderless"# guide to Material design
- 要求sdk29以上
- 主题是 Theme.MaterialComponents下
- 每一种主题都有Bridge,但我觉得这回搞得更混乱
- You should not use the com.android.support and com.google.android.material dependencies in your app at the same time. 不要混用,最好使用androidx 和 com.google.android.material- 里面还有一个如何 从 AppTheme到Meterial的过渡
- 也可以单独的使用一个material design widget
com.google.android.material.textfield.TextInputLayout +
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"# theme/style: @ vs ?
- @color/xx 表示自己定义的确定颜色
- @android:color/xx 系统定义的确定颜色
- "?attr/colorAccent" 跟随自定义theme中某个属性值,也就是某item值
- "?android:attr/colorPrimary" 跟随系统定义theme中某个属性值
- 对于?来说,这个值在不同的theme里面会使用不同的值,而@来说是固定的不随theme而变化
- 这篇文章要求我们更多的使用属性而不是确定值,并列出了很多常用属性# codelab
## 颜色#AARRGGBB
- AA在最前面,FF是不透明,00是全透明
- 如果没有AA, 则 #RRGGBB,此时默认的AA是FF- If an alpha value is not included, it is assumed to be #FF = 100% opaque
- 貌似如 #FFF 也可以工作,但最好是写全:#AARRGGBB##
- A style can specify attributes for a View, style仅仅针对一个view
- A theme is a collection of styles that's applied to an entire app, activity, or view hierarchy—not just an individual View. theme则会影响更多,取决于范围
- FAB 是Material design里面独有的,AppCompat并没有,因此要全称引用
com.google.android.material.floatingactionbutton.FloatingActionButton
- ConstraintLayout的几个关键位置属性NS居然是app,而不是android 如
app:layout_constraintTop_toTopOf="parent"
- AS4.1之后,theme和style使用不同的文件,themes.xml/styles.xml
- Color tool
-
2.7
# style * theme 总结
# 简单修改Toolbar的背景颜色:
- 主题里面的colorPrimary
- 也可以通过直接修改Toolbar的backgroud# 修改Toolbar上title的颜色
- 直接设置在Toolbar上
app:titleTextColor="@color/primary_text"
app:subtitleTextColor="@color/secondary_text"
-# 修改Toolbar上Navigation icon(3条横线)-修改Toolbar上Menu icon(3个点)
<style name="Theme.FundBasic.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar">
<item name="drawerArrowStyle">@style/DrawerIconStyle</item>
<item name="android:actionOverflowButtonStyle">@style/OverflowButtonStyle</item>
</style><style name="DrawerIconStyle" parent="Widget.AppCompat.DrawerArrowToggle">
<item name="color">#333</item>
</style>
<style name="OverflowButtonStyle" parent="Widget.AppCompat.ActionButton.Overflow">
<item name="android:tint">#333</item>
</style>
目前只能说这个方法是可行的,是否最好不一定。# 修改Toolbar上的Action Button的颜色(如果不是图标)
# 一般AppCompatButton
- 显式使用 androidx.appcompat.widget.AppCompatButton
- 最好的方式使用 style
- android:background设置selector
- android:textColor设置前景色# MaterialButton(不是AppCompatButton)默认的颜色,也是没有设置的情况下
- 默认的背景色是 theme下 colorPrimary
- 默认的前景色是 theme下 colorOnPrimary
- 使用style# AppCompatButton
- 没有使用theme下的一般颜色:我看到的是灰色底+黑色字
- 可以通过 theme下的 android:textColorPrimary修改字体颜色#
- 主要的theme会设置在application上: theme.XX
- activity目前的theme基本上是固定theme.XX.NoActionBar,windowActionBar/false, windowNoTitle/true
- Toolbar的theme: theme.XX.AppBarOverlay- 状态栏颜色,在theme里面
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
2.5
# toolbar colors
- colorPrimary – The color of the app bar.
- colorPrimaryDark – The color of the status bar and contextual app bars; this is normally a dark version of colorPrimary.
- colorAccent – The color of UI controls such as check boxes, radio buttons, and edit text boxes.
- windowBackground – The color of the screen background.
- android:textColorPrimary – Button的字体默认颜色,以及大的textView
- android:textColorSecondary - 一般textview字体颜色
- statusBarColor – The color of the status bar.
- navigationBarColor – The color of the navigation bar.# toolbar 的颜色的修改
- popupTheme: 弹出menu的主题颜色,默认跟theme一样- 更改 toolbar的颜色:application的theme
<item name="colorPrimary">#FFF</item>- 更改Drawer那个箭头的颜色,也叫Navigation icon。在toolbar的theme里面:
<item name="drawerArrowStyle">@style/DrawerIconStyle</item>
- 修改Drawer的颜色
<style name="DrawerIconStyle" parent="Widget.AppCompat.DrawerArrowToggle">
<item name="color">#333</item>- 更改三个点munu那个颜色,这个东西是个button
<style name="OverflowButtonStyle" parent="Widget.AppCompat.ActionButton.Overflow">
<item name="android:tint">#333</item>
</style>
- 在toolbar的theme里面添加
<item name="android:actionOverflowButtonStyle">@style/OverflowButtonStyle</item>
# 更改toolbar的title颜色
- Toolbar -> app:titleTextColor="#333"只能说WTF,material.io上有比较完整的介绍#
- toolbar中默认的行为:<Toolbar
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:navigationIcon="?attr/homeAsUpIndicator"- 也就是说这两个属性可以直接修改,但是navigation icon和menu的icon就不同了,他们在theme里面,通过style设置
-#
- 以前是ActionBar,后来ActionBar废弃了,现在是Toolbar
- activity.supportActionBar 返回当前的Bar
- 22的时候有一个ActionBarActivity,但是已经废弃
- 专门有一个类ActionBarDrawerToggle来做Navigation的行为
- # ns: android vs app
- android指
- app: 指support library
- 问题:androidx下呢
- 这儿指出app并不是支持所有的level,或者是custome
也就是说我们尽可能避免使用app
2.4
# Toolbar
- android:background="?attr/colorPrimary"
- app:titleTextColor="@color/primary_text" 直接设颜色
- app:subtitleTextColor="@color/secondary_text"# recyclerview
- 横向的rv: layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
- 在layout设置orientation不起作用
- setHasFixedSize(true) 如果card是大小固定的,则这个有优化作用
- clipToPadding 这个属性很有用,和padding配合起来就是iOS的contentInset
- # accessibility
- android:contentDescription
- TextView和Button默认就是所带文本
- 不需要设置类型,只需要文本即可
- ImageView需要设置,如果不读则设置为"@null"
- 可以给Group如一个layout设值,或者是一个CardView
- ?可以我目前还不知道怎么使用 settings->accessibility->talkback# framgment
- 在fragment里面添加子fragment
childFragmentManager.commit {
add(R.id.asset_card, AssetFragment())
add(R.id.asset_card, AssetFragment())
add(R.id.asset_card, AssetFragment())
}
- 还可以使用一个tag参数,
- childFragmentManager.findFragmentByTag() 可以使用这个查找
- 一般在手动添加fragment会使用一个place holder,如 <FrameLayout />
- # style & theme
- style 和 theme都在 res/values/styles.xml(or themes.xml)
- style可以认为仅仅把layout中widget一些属性放在一起,从而实现重用
- style: <item name="android:paddingStart">20dp</item>
- style可以继承,同时属性值可以override
<style name="btn_primary_green" parent="btn_regular">
- style是针对具体widget
- theme是针对更大一点的东西,包括 Application Toolbar NavigationDrawer ActionBarContextView AppBarLayout
- 区别1:First, attributes assigned to a view via style will apply only to that view, while attributes assigned to it via android:theme will apply to that view as well as all of its children. Style只是针对一个具体的widget,而theme应用到container上能够扩展到子view上
- 区别2: 有些widget只能使用theme,比如这儿提到的checkbox,以及这儿
- style里面可以引用theme,反过来也一样,形式上完全一样。# selector
- 在style里面定义
<item name="android:background">@drawable/btn_gray_bg_selector</item>
- selector是tag
- 常用的状态: state_enabled state_pressed
- 没有状态的时候就是 一般显示状态
- 全部状态在这儿,比如focus一般不会使用
- 全称是 Color state list resource,一般用于颜色和背景颜色
- 如果是背景,在shape外面会加一个 <inset>,然后调整inset的属性,类似与padding,这样就能增大比如Button的点击范围--我猜测
- 这里是一个完整的state的bg
<inset xmlns:android="http://schemas.android.com/apk/res/android">
<shape android:shape="rectangle">
<corners android:radius="25dp"/>
<solid android:color="@color/logo_green"/>
</shape>
</inset>
- 纯静态的东西是没有必要使用selector的#
2.3
# Button
- android:textAllCaps 设置成false,否则都是大写
- 使用<androidx.appcompat.widget.AppCompatButton 而不是Button WTF!
- 为什么我在使用androidx的情况下还用了Button?
- When you are using a Button or an EditText you are actually using AppCompatButton and AppCompatEditText. From the official documentation of the AppCompatEditText. 这儿听上去很对,但是实际的情况是我使用了AppCompatActivity,结果Button却是老的。
- 所有的基本widget在AppCompatActivity下都有一份
- 由于CompatWidget和老widget的很多设置不一样#
- TexgtView属性里面有一项 android:ellipsize="end",意思是压缩显示,跟iOS上那个一样,可以是start/end或者middle
- style中,可以包含多项,但是要注意力度,否则就会把这个东西搞得乱七八糟,尤其是一些不常用的参数不要包含进去,比如对于TextView,只包含fontSize fontStyle 以及 textColor即可
- # data binding & view binding
- 在layout文件中看到 tag <layout>作为顶层,这是data binding。当然也可以不使用data binding,所以就会看到 layout在顶层
- data binding: gradle--> android->buildFeatures-> dataBinding = true
- view binding也一样: viewBinding = true
- data/view binding 是两个不同的东西,
- view binding是在代码中直接调用layout的元素,后面是AS插件帮我们讲layout编译成代码
- data binding是在layout文件中直接使用代码变量
- view binding: 生成的文件的命名--layout文件的名字# kotlin
- class Person constructor(firstName: String) { /*……*/ } constructor是关键字,大部分时间可以省略,这也是主构造函数
- 主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块(initializer blocks)中。
- init是关键字,不是函数,没有参数
- init只能访问主构造函数中的输入参数
- class Person(val firstName: String, val lastName: String, var age: Int) { /*……*/ } 可val/var,这省略了constructor
- 次构造函数,类似swift里面的convenient
- # fragment lifecycle & from android
- onAttach()/onDetach()是最前和最后,
- 然后是onCreate()/onDestroy()
- 然后是onCreateView()/onDestroyView()
- 然后 onStart()/onStop()
- 然后 onResume()/onPause()
- onViewCreated() 和 onViewStateRestored() 在onCreateView之后
- onActivityCreated也在onCreateView之后,但现在被废弃,要求自己监视Activity的生命周期
- # viewModel的获取
- val vm = ViewModelProvider(this).get(HomeViewModel::class.java)
- val vm = viewModels<HomeViewModel>() 必需是val
- val vm: HomeViewModel by viewModels() 必需是val
- viewModels()这个本身就是lazy的
- 在比较方便的情况下,无需cache vm,直接当属性,即取即用- val vm = ViewModelProviders.of(this).get(MyViewModel::class.java) 这个已经废弃
- ViewModel 是个class,而不是interface# ViewModel vs Presenter
- ViewModel主要是cache数据
- 而Presenter是数据与UI的交互
- 我们可以认为Presenter也是一个ViewModel,也就是利用VM的特性
- 但ViewModel可以更细致一些
- 比如一个page我们最好只用一个Presenter,但我们可以使用多个VM,而且可以让这些VM完全独立,比如页面的状态。
2.2
#
- ViewPager在UI上只能显示当前item,而且是全屏
- ViewPager可以和Tab合用:layout中ViewPager下包含一个TabLayout
- 使用ViewPager2
- ViewPager vs RecyclerView 实现跑马灯:前者是全屏,后者不是,不要硬凑
- ViewPager更类似与Screen slide的方式
- PagerSnapHelper 与 RecyclerView配合使用:帮助后者自动到中间
- FragmentStatePagerAdapter这个类是把Fragment作为数据源,通常是用在ViewPager里面
- ViewPager有自己的Adapter:PagerAdapter# NestedScrollView
- Swiperefreshlayout:一般作为顶层layout,NestedScrollView为第一子layout
- ScrollView supports vertical scrolling only. For horizontal scrolling, use HorizontalScrollView instead. ScrollView只有垂直方向
- Never add a RecyclerView or ListView to a ScrollView. Doing so results in poor user interface performance and a poor user experience. 不要在ScrollView里面添加RecyclerView或者是ListView
- NestedScrollView里面可以添加RecyclerView
- NestedScrollView也仅仅support vertical,如果想要一个横着,可以直接使用RecyclerView#
- Google建议除了SwipeRrefreshLayout,还有就是手动刷新,放在menu里面
- OnRefreshListener监听接口
- 也可以直接 setOnRefreshListener{}
- isRefreshing 控制loading spinner
- View.post {}, View.postDelayed(1000L){} Causes the Runnable to be added to the message queue. The runnable will be run on the user interface thread. 可以理解给UI线程发送消息,此处不需要什么mHandler
- View.post{}这个是很简单的保证在UI线程中执行,当然也可以使用coroutine,但是这个也很简单。#
- layout_weight: 只针对LinearLayout
- # identity function
- identity 函数数学上就是恒等函数 f(x) = x
- kotlin: fun <T> identity(x: T): T = x
- or fun <T: Any> T.identity(): T = this
- 使用identity,也就是某个地方需要一个函数:methodx(::identity)
- arrow: fun <A> identity(a: A): A = a