AAD-21-2-items

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
# EditTextPreference 属性配置:summary显示输入值
- 老版本不支持显示输入值,需要一些hack方法
- ''androidx.preference:preference:1.1.1' 之后有 useSimpleSummaryProvider
- ns是 app:useSimpleSummaryProvider
- 最终的效果是 输入值直接占据 summary的为止
- 如果是
# 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# EditText的输入类型: 
- numberDecimal 仅仅能输入正小数值
- android:inputType="numberDecimal|numberSigned" 才能输入负数
- 其他还有很多类型: "phone", "textPassword"
- 具体见这儿
# ViewModel的一种用法--仅仅数据
- ViewModel的数据来源:在我厂的代码里面,ViewModel仅仅是保留数据,
- 而是在外部设置viewModel中LiveData的值,因此ViewModel就变得很简单。
- 也就是不再是presenter的作用,因此不需要要考虑线程和coroutines的问题
- 也就是只有数据,没有方法
- 而另外有presenter负责数据获取
# 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
# 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# 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
# 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
-
# 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区域
# 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直接跳转
# 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)
# 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
-
# 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>
# 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
# 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的
#
# 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完全独立,比如页面的状态。
# 
- 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

--

--

--

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Building a process to help learning (as a software developer)

Azure Active Directory Connect (AADC) Part 3 — Active Directory Federation Service(ADFS) and Web…

Ionic 4 Add To Cart Example Using PHP, MySQL

Docker Essentials for Developers

Cool YAML features you probably didn’t know

Using Azure AD B2C to Authenticate Web App Users

This photo shows a door with very heavy chains and a padlock. The door is intended to represent a protected web application that the user needs to log into before being able to use it.

Shattered Pixel Dungeon Mod Apk 1.2.3 (Unlimited money)

Real-time risk analytics with python and atoti

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
XollXoll

XollXoll

More from Medium

Food for your mood strategic plan! Box delivery x Polishing Reputat

Acquaintance with ZkPad☄️

Evolving Through Self-Awareness with Christina Howard

(Z)AP Lang