一周安卓开发问题汇总总结(一)
Opened this issue · 0 comments
-
前言
最近忙着开发,对博客的输出暂缓,但是开发中碰到的问题,还需要有个地方随时记录,随时总结经验。主要还是UI层面为主为了下次开发更加顺畅。
-
暗黑模式问题
安卓项目在建立的时候会默认生成一个themes.xml的主题配置
还有一个themes.xml是在value-night文件夹里面,对应的qualifier为night。
这个配置对应我们手机上的深色模式,有的手机的神色模式是随时间的变化配置的。问题我们项目的设计暂时只设计一套UI,所以深色模式下显示不同的UI显得毕竟奇怪。
最一劳永逸的解决方式
-
修改themes的parent
把原来的Theme.MaterialComponents.DayNight 改成. NoActionBar Theme.MaterialComponents.Light.NoActionBar -
删除 value-night下themes.xml
这里要注意,斩草要除根。除了删除app下的themes.xml,还要检查所有module下的themes,因为安卓资源的打包方式是merge,所以我就出现了只删除app下的night的themes.xml而暗黑模式还存在造成怀疑人生。
-
BottomSheetDialog增加圆形导角问题
学会了BottomSheetDialog的基本用法,但是在xml加了Card的圆角也并不能显示导角,还是四四方方的弹框。
圆角并不能在BottomSheetDialog的内容中设置
需要从AppTheme入手
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white"/>
<corners android:topLeftRadius="16dp"
android:topRightRadius="16dp"/>
</shape>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="bottomSheetDialogTheme">@style/AppBottomSheetDialogTheme</item>
</style>
<style name="AppBottomSheetDialogTheme"
parent="Theme.Design.Light.BottomSheetDialog">
<item name="bottomSheetStyle">@style/AppModalStyle</item>
</style>
<style name="AppModalStyle"
parent="Widget.Design.BottomSheet.Modal">
<item name="android:background">@drawable/rounded_dialog</item>
</style>
-
相关资料
-
theme 设置问题
篇幅太大,另外展开
-
Spinner的文字和下拉图标间距问题
解决方案简单粗暴 android:padding="0dp"
但是一下子就是找不到找不到!
-
Spinner的高度设置问题
不知道安卓的UI团队是怎么想的
可以通过android:dropDownWidth来设置宽度,但是没有设置高度的地方,似乎高度就一定和内容项绑定
StackOverflow上的高赞回答是用反射来强行设置,似乎高版本已经不起作用了,暂时就是“无解”
-
How to limit the height of Spinner drop down view in Android
-
Spinner选中项文字高亮显示问题
Spinner控件实在是不熟,目前解决方式就是高度自助话处理。直接override
public static class SimpleArrayAdapter<T> extends ArrayAdapter<T>{
private final List<T> list;
private final int tvResource;
private int selectedPosition;
private final int iconResource;
private final int icon;
public SimpleArrayAdapter(Context context, @LayoutRes int resource,@IdRes int tvResource,@IdRes int iconResource,@DrawableRes int icon,
List<T> objects){
super(context,resource, tvResource ,objects);
this.list = objects;
this.tvResource = tvResource;
this.iconResource = iconResource;
this.icon = icon;
}
public void setSelectedPosition(int selectedPosition) {
this.selectedPosition = selectedPosition;
}
@Override
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View view = super.getDropDownView(position, convertView, parent);
if( tvResource > 0 ) {
TextView textView = view.findViewById(tvResource);
textView.setTextColor( selectedPosition == position ? Color.BLUE : Color.BLACK );
}
return view;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View view = super.getView(position, convertView, parent);
if( iconResource > 0 && icon > 0 ){
ImageView imageView = view.findViewById(iconResource);
imageView.setImageResource(icon);
}
return view;
}
}
通过getDropDownView的重载来设置文字的背景色
@SuppressLint("ResourceType")
@BindingAdapter(value = {"itemsSource","onItemSelected","selectedIndex","resource","tvResource","dropdownResource","iconResource","icon"},requireAll = false)
public static <T> void setSpinnerItems(Spinner spinner , List<T> itemsSource, final OnMyItemSelectedListener listener , int index, @LayoutRes int resource, @IdRes int tvResource, @LayoutRes int dropdownResource , @IdRes int iconResource, @DrawableRes int icon){
SimpleArrayAdapter<T> adapter;
@SuppressWarnings("unchecked")
SimpleArrayAdapter<T> oldAdapter = (SimpleArrayAdapter<T>)spinner.getAdapter();
boolean createNew = oldAdapter != null && oldAdapter.list == itemsSource;
if( createNew ){
adapter = oldAdapter;
adapter.notifyDataSetChanged();
}
else {
int viewResource = resource > 0 ? resource : android.R.layout.simple_spinner_item;
adapter = new SimpleArrayAdapter<>(spinner.getContext(), viewResource, tvResource, iconResource , icon , itemsSource);
adapter.setDropDownViewResource( dropdownResource > 0 ? dropdownResource : android.R.layout.simple_spinner_dropdown_item);
if( listener != null ) {
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private Optional<Integer> lastPosition = Optional.empty();
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
//String itemValue = (String) parent.getItemAtPosition(position);
if (lastPosition.isPresent() && lastPosition.get() == position) {
return;
}
lastPosition = Optional.of(position);
adapter.setSelectedPosition( position );
listener.onItemSelected(position);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
}
spinner.setAdapter(adapter);
if( spinner.getSelectedItemPosition() != index ) {
spinner.setSelection(index);
}
}
这是我Spinner 的绑定适配器,配合一起使用的,由于当时觉得不是太复杂,走的思路不是MVVM。现在属性多了有点惨不忍睹。日后一定是要重构的,只是完成现阶段需求。
-
Material Design控件应用简介
安卓的界面基础是从官网进去 界面和导航
不过还不能完全符合需求
material控件库基本上算是谷歌的官方库了。
只要依赖声明一下就行很方便
implementation 'com.google.android.material:material:1.4.0'
具体用法参考
material控件库
Android是官方支持,而且api文档用例齐全,是安卓开发的常用网站
-
适配多种手机分辨率的弹性布局方案
-
从专用设备到通用移动设备转型的阵痛
以前开发的专用屏,现在开发安卓手机端通用app。第一个变化是没法root了。当然没有啥“变态”功能还能忍。
最大的变化是不能把界面“做死”
不同的手机分辨率是由细微差别的。以前做专用设备,统一的屏幕统一的分辨率。控件布局啥的“焊死”得了,一点问题都没有。现在做手机的应用是老办法就不行了。控件重叠,间距难看等问题都来了。 -
思路转变
控件不能写死dp,而是适配的方式,比如宽度“顶住”左右控件但是不设置大小,只固定边距,宽度直接和屏幕大小适配
如图constraint-layout支持控件平均分配以及按权重分配
如图6个输入框是按屏幕宽度平均“瓜分”的,先把中间的分割线以及固定边距留下了剩下“平分” -
“随心”的控制文本框的焦点
如果这些验证码都要一个一个自己切实在太麻烦了,要怎么让他自然切换那?
好在TextInputEditText有afterTextChanged事件,通过binding库的配合实现
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/code_text_input_1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:afterTextChanged="@{()->dataContext.inputCode( dataContext.code1 , codeTextInput2 )}"
android:inputType="number"
android:maxLength="1"
android:padding="0dp"
android:text="@={dataContext.code1}"
android:textSize="18sp" />
</com.google.android.material.textfield.TextInputLayout>
public void inputCode( String current , View next ){
//MessageHelper.ShowToast( this.getApplication(), "text changed:" + current );
if(! StringUtils.isTrimEmpty( current ) ){
next.requestFocus();
}
}
这样就能实现输入焦点自动切换了。当然还有一些细节并没有做到完美,比如最后一个输入框应该输入后自动隐藏输入法等。慢慢完善吧。
-
Navigation中模态Fragment和普通Fragment的通信问题
-
Navigation的单向性的问题
如图fragment->dialog是OK的,dialog->fragment导航图可以画出来,运行给你分分钟报错!!!
冷静一下也合理,fragment和dialog是不平等的,其实我们平时写dialog也是fragment调用的,就是dialog是fragment的下级,下级是无法转为“上级”的。
那fragment怎么拿到dialog的结果
1 viewModel共享
这个前面博客有介绍,但是代价有点大。viewModel的生命周期延长了。一级页面还能接受(本来就是堆栈缓存的),二三级页面就有点不可接受了
2 FragmentResultListener
可以实现
private void closeSelf( String url ){
this.dismiss();
Bundle result = new Bundle();
result.putString("url", url);
getParentFragmentManager().setFragmentResult("requestKanbanUrl", result);
}
这是dialog的实现
getParentFragmentManager().setFragmentResultListener("requestKanbanUrl", this, (requestKey, bundle) -> {
String url = bundle.getString("url");
Logger.i( "receive url: %s",url );
NavDirections action = KanbanFragmentDirections.actionKanbanFragmentToMobileWebFragment(url);
this.disposables.add(Single.timer( 100, TimeUnit.MILLISECONDS )
.observeOn(AndroidSchedulers.mainThread())
.subscribe( t-> NavHostFragment.findNavController(this).navigate(action) ) );
});
这是fragment的FragmentResultListener,
通过delay来进行跳转
因为FragmentResult获取在前,dialog关闭在后,跳转必须要在dialog关闭以后,所以不得不加了一把延时,确实有一点点奇葩。所以以后这种情况要考虑dialog是否要踢出导航了
-
gradle加速问题解决
当“翻墙”也不能挽救gradle下载的时候,就只能靠镜像了
repositories {
maven { url 'https://maven.aliyun.com/repository/central' }
maven { url 'https://maven.aliyun.com/repository/google' }
google()
mavenCentral()
}
在project级别上的build.gradle加上两行maven。
速度飞快,很好
相关参考站点
先罗列再填坑