我因为要开发与工作相关的安卓app,而开始接触Java语言和Android Studio。为了更好的学习,我购买了欧阳燊先生编著的《Android App开发入门与项目实战》,这本书给了我很大的帮助,让我的app开发能够顺利进行。
最近,我正在给我的app设计数据库的记录和展示功能,在翻阅《Android App开发入门与项目实战》时,发现第八章的实战项目-记账本 实现了用数据库保存输入的记录,并通过ViewPager与 Fragment组合对记录进行展示,正符合我的项目需求。于是我便仔细研究其这个Module的代码来,待研究透了,再着手进行自己app的相关设计。
在使用教程提供的代码在手机上进行测试的过程中,我发现了一个问题。
问题描述
在做【添加账单】功能的测试时,我分别在2022年10月和2023年10月都保存了支出数据,在【账单列表】 模块里,进入时的默认月份是当前月份(2023年10月),app中自动加载的是2023年10月的记录,左右滑动则展示2023年其它月份的记录,这是没问题的。
但是当我将月份改成2022年10月份以后,问题就出现了。
选择完日期后,fragment中的的记录竟然还是2023年10月份的,左右滑动,发现其它月份的记录也是2023年的,并不是我希望看到的2022年的记录。这显然不是我想要的结果。也说明教材源码是存在BUG的。
我如果照着源码开发自己的app,显然也会出现同样的问题,必须要找到解决办法。
解决办法
通过对代码的细致研究,我感觉问题原因应该是在选择日期后,教材的源码没有对fragment的内容进行更新,导致展示出来的还是在其创建时候的内容。我要找到更新fragment内容的办法。
好在CSDN博客里有丰富的资料,通过搜索,我找到了两篇文章,讲到了相关的内容。
关于ViewPager清除已加载Fragment,重新创建新的Fragment的方法
Android ViewPager+Fragment 动态加载
通过研读这两篇文章,我总算搞清楚了问题原因,知道了解决方法。
我尝试着按这两篇文章的内容,对教材的源码进行了修改,经过测试,之前遇到的修改月份到不同年份,fragment的内容无变化的问题得到了解决。我的解决办法大致如下:
首先是对源码中的BillPagerAdpater.java(这是一个FragmentPagerAdapter)进行修改,添加clear方法和makeFragmentName方法。另外我还添加了自己设计的setYear方法,用于改变这个适配器里的私有变量mYear。
然后再对BillPagerActivity.java文件中的onDateSet方法进行修改,增加判断语句,在年份发生变化时,调用setYear方法变更适配器里的年份,使用clear方法清除掉fragment中的内容,再通过对ViewPager使用setAdapter方法,让Ffragment重新生成新的内容。
修改后的代码
为了能让大家更好的理解,我将修改后的BillPagerAdpater.java和BillPagerActivity.java贴出来。
BillPagerAdpater.java
package com.example.chapter08.adapter;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.fragment.app.FragmentTransaction;
import com.example.chapter08.fragment.BillFragment;
public class BillPagerAdpater extends FragmentPagerAdapter {
private int mYear; // 声明当前账单所处的年份
private final FragmentManager mFragmentManager; // 添加的内容
private FragmentTransaction mCurTransaction; // 添加的内容
// 碎片页适配器的构造方法
public BillPagerAdpater(FragmentManager fm, int year) {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
mFragmentManager = fm; // 添加的内容
mYear = year;
}
// 获取碎片Fragment的个数,一年有12个月
public int getCount() {
return 12;
}
// 获取指定月份的碎片Fragment
public Fragment getItem(int position) {
return BillFragment.newInstance(mYear*100 + (position + 1));
}
// 获得指定月份的标题文本
public CharSequence getPageTitle(int position) {
return (position + 1) + "月份";
}
// setYear方法、clear方法、makeFragmentName方法都是添加的内容
// 重新设置年份
public void setYear(int year) {
mYear = year;
}
/**
* 清除缓存fragment
* @param container ViewPager
*/
public void clear(ViewGroup container){
if (this.mCurTransaction == null) {
this.mCurTransaction = this.mFragmentManager.beginTransaction();
}
// 根据源码对for语句进行了修改,将mFragmentArrayList.size()改成了12
// 不改的话允许时会出错,这个项目中固定了fragment的数量为12
for(int i=0;i<12;i++){
long itemId = this.getItemId(i);
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = this.mFragmentManager.findFragmentByTag(name);
if(fragment != null){//根据对应的ID,找到fragment,删除
mCurTransaction.remove(fragment);
}
}
mCurTransaction.commitNowAllowingStateLoss();
}
/**
* 等同于FragmentPagerAdapter的makeFragmentName方法,
* 由于父类的该方法是私有的,所以在此重新定义
* @param viewId
* @param id
* @return
*/
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
}
BillPagerActivity.java
package com.example.chapter08;
import android.app.DatePickerDialog;
import android.content.Intent;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.View;
import android.widget.DatePicker;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager.widget.PagerTabStrip;
import androidx.viewpager.widget.ViewPager;
import com.example.chapter08.adapter.BillPagerAdpater;
import com.example.chapter08.util.DateUtil;
import java.util.Calendar;
public class BillPagerActivity extends AppCompatActivity implements
View.OnClickListener, DatePickerDialog.OnDateSetListener, ViewPager.OnPageChangeListener {
private TextView tv_month;
private ViewPager vp_bill; // 声明一个翻页视图对象
private Calendar calendar = Calendar.getInstance(); // 获取日历实例,里面包含了当前的年月日
BillPagerAdpater adapter; // 源码是局部变量,我改成了全局变量
private int mYear; // 我添加的,保存年份,在修改日期后会用到这个变量
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bill_pager);
TextView tv_title = findViewById(R.id.tv_title);
TextView tv_option = findViewById(R.id.tv_option);
tv_month = findViewById(R.id.tv_month);
tv_title.setText("账单列表");
tv_option.setText("添加账单");
findViewById(R.id.iv_back).setOnClickListener(this);
tv_option.setOnClickListener(this);
tv_month.setOnClickListener(this);
tv_month.setText(DateUtil.getMonth(calendar));
// 从布局视图中获取名叫vp_bill的翻页视图
vp_bill = findViewById(R.id.vp_bill);
initViewPager(); // 初始化翻页视图
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.iv_back) {
finish(); // 关闭当前页面
} else if (v.getId() == R.id.tv_option) {
Intent intent = new Intent(this, BillAddActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // 设置启动标志
startActivity(intent); // 跳到账单填写页面
} else if (v.getId() == R.id.tv_month) {
// 构建一个日期对话框,该对话框已经集成了日期选择器。
// DatePickerDialog的第二个构造参数指定了日期监听器
DatePickerDialog dialog = new DatePickerDialog(this, this,
calendar.get(Calendar.YEAR), // 年份
calendar.get(Calendar.MONTH), // 月份
calendar.get(Calendar.DAY_OF_MONTH)); // 日子
dialog.show(); // 显示日期选择对话框
}
}
@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month);
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
// 以下是添加的内容
int selectedYear = calendar.get(Calendar.YEAR);
if (selectedYear != mYear) {
mYear = selectedYear; // 更新全局变量,用于后续的年份判断
adapter.setYear(mYear); // 更新fragment的mYear数据
adapter.clear(vp_bill); // 清除已有的fragment
vp_bill.setAdapter(adapter); // 设置翻页视图的适配器
vp_bill.setCurrentItem(calendar.get(Calendar.MONTH)); // 设置翻页视图显示第几页
vp_bill.addOnPageChangeListener(this); // 给翻页视图添加页面变更监听器
}
tv_month.setText(DateUtil.getMonth(calendar));
vp_bill.setCurrentItem(month); // 设置翻页视图显示第几页
}
// 初始化翻页视图
private void initViewPager() {
// 从布局视图中获取名叫pts_bill的翻页标签栏
PagerTabStrip pts_bill = findViewById(R.id.pts_bill);
// 设置翻页标签栏的文本大小
pts_bill.setTextSize(TypedValue.COMPLEX_UNIT_SP, 17);
// 构建一个商品图片的翻页适配器
adapter = new BillPagerAdpater(getSupportFragmentManager(), calendar.get(Calendar.YEAR));
vp_bill.setAdapter(adapter); // 设置翻页视图的适配器
vp_bill.setCurrentItem(calendar.get(Calendar.MONTH)); // 设置翻页视图显示第几页
vp_bill.addOnPageChangeListener(this); // 给翻页视图添加页面变更监听器
}
// 翻页状态改变时触发
public void onPageScrollStateChanged(int state) {}
// 在翻页过程中触发
public void onPageScrolled(int position, float ratio, int offset) {}
// 在翻页结束后触发
public void onPageSelected(int position) {
calendar.set(Calendar.MONTH, position);
tv_month.setText(DateUtil.getMonth(calendar));
}
}
改进了教材源代码后,我的app的也顺利完成了数据记录的保存和展示功能,经过测试,没有出现上述的BUG。