以下内容为笔者在开发天气预报App时所遇到的实际问题及其解决方法,在此记录与读者分享也供自己日后回顾。
在开发时,我们可能需要在同一活动中的不同界面下来改变布局的位置使得布局处于一个比较合理的位置。
例如:我们在活动的主界面希望用户能有沉浸式的体验,所以我们将我们的布局延伸到了系统栏中,可是当我们转换到设置(碎片)的界面时,就很有可能发生系统栏中的图标,将我们界面上标题栏的部分按钮遮挡的情况。而为了解决这一问题我们就要在代码中对布局位置进行控制了。
代码展示:
FrameLayout.LayoutParams layoutParams;
layoutParams=(FrameLayout.LayoutParams) view.findViewById(R.id.choose_layout).getLayoutParams();
if(getActivity() instanceof WeatherActivity){
//动态控制布局的margin属性
layoutParams.topMargin=50;
}
在开发时,很多时候都会让用户选择让某一项功能开启还是关闭,这个时候就需要一个比较美观的控件来实现这一功能了。
//声明依赖
dependencies {
implementation 'com.github.zcweng:switch-button:0.0.3'
}
//布局文件中用法
<com.suke.widget.SwitchButton
android:id="@+id/switch_button"
android:layout_gravity="center"
android:layout_marginLeft="60dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
//逻辑文件中用法
switchButton.setChecked();//设置控件的开关状态
switchButton.setOnCheckedChangeListener(new SwitchButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(SwitchButton view, boolean isChecked) {
//点击事件
if(isChecked==true){
//当控件变为true时的逻辑代码
}else if(isChecked==false){
//当控件变为false时的逻辑代码
}
}
});
在开发时,我们可能会需要先隐藏部分布局,然后当用户执行某一操作后再将这部分布局显现出来。为了实现这一功能我给大家介绍两种方法,并为大家指出它们的利弊。
我们需要先将需隐藏布局的可见属性设置为View.GONE,然后在用户执行某一操作后将可见属性设置为View.VISIBLE即可轻易实现这一功能。
我们也可以使用ViewStub控件来帮助我们实现这一功能。
代码展示:
//待隐藏布局
<?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:orientation="vertical" >
<EditText
android:id="@+id/edit_extra1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:hint="Extra field 1" />
<EditText
android:id="@+id/edit_extra2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:hint="Extra field 2" />
<EditText
android:id="@+id/edit_extra3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:hint="Extra field 3" />
</LinearLayout>
//主界面布局
<?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:orientation="vertical" >
<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:hint="@string/edit_something_here" />
<Button
android:id="@+id/more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginRight="20dp"
android:layout_marginBottom="10dp"
android:text="More" />
<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/profile_extra"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
//逻辑代码
private EditText editExtra1;
private EditText editExtra2;
private EditText editExtra3;
public void onMoreClick() {
//当我们点击more按钮时执行该函数,即可是实现隐藏布局的显示
ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub);
if (viewStub != null) {
View inflatedView = viewStub.inflate();
editExtra1 = (EditText) inflatedView.findViewById(R.id.edit_extra1);
editExtra2 = (EditText) inflatedView.findViewById(R.id.edit_extra2);
editExtra3 = (EditText) inflatedView.findViewById(R.id.edit_extra3);
}
第一种方法:
优点:逻辑简单而且控制起来比较灵活。
缺点:耗费资源。虽然把View的初始可见View.GONE但是在Inflate布局的时候View仍然会被Inflate,也就是说仍然会创建对象,会被实例化,会被设置属性。也就是说,会耗费内存等资源。
第二种方法:
优点:在Inflate布局的时候,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时候,或是调用了ViewStub.inflate()的时候,ViewStub所向的布局就会被Inflate和实例化,然后ViewStub的布局属性都会传给它所指向的布局。这样,就可以使用ViewStub来方便的在运行时,要还是不要显示某个布局。这样也就节省了内存资源,实现了布局的优化。
特点:ViewStub只能Inflate一次,之后ViewStub对象会被置为空。也就是说,某个被ViewStub指定的布局被Inflate后,就不能再通过ViewStub来控制它了;ViewStub只能用来Inflate一个布局文件,而不是某个具体的View,当然也可以把View写在某个布局文件中。
缺点:在程序的运行期间,某个布局在Inflate后,就不会有变化,除非重新启动。也就是说,不能再将加载出来的布局隐藏了;只能控制显示与隐藏的是布局文件,而非某个View。
在开发时,我们有可能要根据ImageView展示的图片,来决定让其执行哪一点击事件。而要实现这一功能就需要判断先ImageView展示的是哪一张图片。
代码展示:
moreImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(moreImage.getDrawable().getCurrent().getConstantState().equals(ContextCompat.getDrawable(SettingActivity.this,R.drawable.many).getConstantState())){
//判断方法
//具体逻辑
}else {
//具体逻辑
}
}
});
在开发时,我们有时需要提供一组选项,来让用户选择一个。而实现这一功能就要利用单选框控件了。
代码展示:
//布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RadioGroup
android:id="@+id/radio_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RadioButton
android:id="@+id/eight_time"
android:layout_margin="10dp"
android:text="8小时"
android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<RadioButton
android:id="@+id/four_time"
android:layout_margin="10dp"
android:text="4小时"
android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<RadioButton
android:id="@+id/two_time"
android:layout_margin="10dp"
android:text="2小时"
android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<RadioButton
android:id="@+id/one_time"
android:layout_margin="10dp"
android:text="1小时"
android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RadioGroup>
</LinearLayout>
//逻辑代码
radioGroup.check();//设置哪一单选框为选中状态
radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
//点击事件
@Override
public void onCheckedChanged(RadioGroup radioGroup, int i) {
switch (i){
case R.id.eight_time:
//选中后的逻辑代码
break;
case R.id.four_time:
//选中后的逻辑代码
break;
case R.id.two_time:
//选中后的逻辑代码
break;
case R.id.one_time:
//选中后的逻辑代码
break;
default:
}
}
}
在开发中,我们有时需要实现侧滑RecyclerView的某一子项布局来删除它的功能。
以下代码中省略了RecyclerView的基本实现方法,倘若有读者还不太清楚,请移步这里
代码展示:
//为RecycleView绑定触摸事件
ItemTouchHelper helper=new ItemTouchHelper(new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
//首先回调的方法 返回int表示是否监听该方向
int dragFlags = ItemTouchHelper.UP|ItemTouchHelper.DOWN;//拖拽
int swipeFlags = ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT;//侧滑删除
return makeMovementFlags(dragFlags,swipeFlags);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
//滑动事件
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
//侧滑事件
nameList.remove(viewHolder.getAdapterPosition());
myAdapter.notifyItemRemoved(viewHolder.getAdapterPosition());
}
});
helper.attachToRecyclerView(recyclerView);
注意:倘若你还需在侧滑事件中删除数据库的内容,要将其放在remove函数之前,否则getAdapterPosition()的返回值会发生变化。
当我们在处理RecyclerView的点击事件时很有可能还要用到活动中的一些数据,而一般情况下RecyclerView的点击事件是在其适配器中注册的,这就使得你可能无法在点击事件中实现某些功能了。而为了解决在活动中处理点击事件这一问题,我们可以利用回调接口来实现。
代码展示:
//定义接口
public interface OnItemClickListener {
void onItemClick(int position);
}
//在适配器的点击事件中调用该接口中的方法
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private List<Saved> list=new ArrayList<>();
private OnItemClickListener Listener=null;
public MyAdapter(List<Saved> list, OnItemClickListener Listener){
//我们还需要在构造方法传入该接口
this.Listener=Listener;
this.list=list;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, final int viewType) {
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item,parent,false);
final ViewHolder viewHolder=new ViewHolder(view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Listener.onItemClick(viewHolder.getAdapterPosition());//调用接口中的方法
}
});
return viewHolder;
}
......
}
//在活动中实现该接口中的方法
OnItemClickListener recyclerListener=new OnItemClickListener() {
@Override
public void onItemClick(int position) {
//具体逻辑代码
}
};
默认情况下Android通知栏的颜色总为紫色,通知栏中的图标颜色总为白色。而这很有可能会影响到我们app界面的美观。
例如:我想将我界面下的图片延伸到通知栏中实现沉浸式体验,这时我们就要将系统通知栏的颜色更改为透明色。再此之后,还有可能还会出现一个问题,那就是如果我的图片颜色为浅色,就很有可能导致通知栏中的图标看不清了,那么此时我们就还需去更改通知栏中图标的颜色。
代码展示:
if(Build.VERSION.SDK_INT>=21){
//注意:更改通知栏的功能,只在Android5.0以上的系统中才支持
View decorView=getWindow().getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LAYOUT_STABLE);//将通知栏中的图标设置为白色。
getWindow().setStatusBarColor(Color.TRANSPARENT);//将通知栏设置为透明色。
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);//将通知栏中的图标颜色设置为黑色。
}
注意:上述方法会将通知栏变为我们布局的一部分,因而导致通知栏的空间也被我们的布局占据。若是想要保留通知栏的空间,我们只需在布局中加入android:fitsSystemWindows="true"属性即可。
目前侧滑栏DrawerLayout已经被广泛的应用到各类app当中了,而当我们自己在使用它时,很有可能会遇到一个问题。那就是,我们想让侧滑栏在活动中打开时,触发相应逻辑;而在活动中关闭时,也触发相应逻辑。而想要解决这一问题就需要给DrawerLayout注册监听事件了。
代码展示:
drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
//DrawerLayout滑动时触发的逻辑。
}
@Override
public void onDrawerOpened(@NonNull View drawerView) {
//DrawerLayout打开时触发的逻辑。
}
@Override
public void onDrawerClosed(@NonNull View drawerView) {
//DrawerLayout关闭时触发的逻辑。
}
@Override
public void onDrawerStateChanged(int newState) {
//DrawerLayout状态改变时触发的逻辑。
}
});
在开发时,我们可能想要实现让用户一边输入我们一边更新界面中的信息这一功能。
例如:在浏览器搜索栏中,我们一边输入信息,浏览器一边为我们更新提示信息;在计算器中,我们一边输入表达式,计算器一边为我们提前显示计算结果。
代码展示:
//我们以浏览器更新提示信息及滑动控件为ListView为例进行说明,
editText.addTextChangedListener(new TextWatcher() {
//首先给editText注册监听事件
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
//输入框信息变化前对应逻辑
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
//输入框变化中对应逻辑
}
@Override
public void afterTextChanged(Editable editable) {
//输入框变化后对应逻辑
//我们用dataList作为ListView的数据集合
provinceList=LitePal.findAll(Province.class);
dataList.clear();
for(Province province:provinceList){
dataList.add(province.getProvinceName());//在每次输入框的内容改变时,我们都要将数据库中的数据完整的放在dataList中等待筛选。因为,输入框中的内容不仅仅会增加,也有可能会减少。
}
final String str=editText.getText().toString();
for(int i=0;i<dataList.size();i++){
if(dataList.get(i).indexOf(str)==-1){
//判断待筛选信息,是否有输入框中的内容
dataList.remove(i);//若与输入框中的内容无关,则将该信息从dataList中移除
i--;//注意:当我们删除一条信息后,dataList中数据对应的位置会发生改变,所以我们这里也要相应改变i.
}
}
adapter.notifyDataSetChanged();//筛选完成后刷新ListView中的内容
}
});
更改软件图标,对于开发者而言,应该是十分常用的操作了。不过笔者根据《第一行代码》中的方法来修改图标,结果却是图标纹丝不动,一点改变也没有。笔者在尝试过各种不同的方法之后,最终总算完成了图标的修改。
解决方法:把你想要修改的图标在每个drawable文件夹下都放置一份,再将AndroidManifest.xml里面的icon文件路径改为drawable下的图标即可。