1.功能分析介绍
知识点
-
ViewPager
:页面的滑动 -
PagerSlidingTabStrip
:第三方的自定义View,使得菜单栏和下面的页面产生联动的效果。 -
ListView
列表视图 -
WebView
控件:详情页面加载网址 - 如何获取网络数据并解析展示
- 数据库的增删改查:需要保留自定义的频道信息,当下一次进入该应用时候,会显示上一次保留的频道信息。
使用第三方框架
-
Volley
框架:是网络加载数据的框架 -
Universal-image-loader
图片加载框架 -
PagerSlidingTabStrip
第三方定义view的使用
逻辑分析
- 首界面为
ViewPager
,上面为PagerSlidingTabStrip
,两个控件可以相互影响,点击“+”,可以跳转到频道订阅界面。 - 频道订阅界面,能够选择首界面显示的新闻类型,改变上一次选择内容返回上级页面时会改变首界面的显示内容,其中头条和社会是默认选项,不能改变。
- 点击首界面列表中的每一条目,会跳转到详细页面,显示新闻的详细信息。
2.页面布局绘制和接口分析
把 background_tab.xml
导入drawable中attrs.xml
是关于自定义View属性的xml文件
第三方view
导入所需要的包
可以嵌套在ViewPager中的Fragment布局
写一个能够将访问的网址都存放的类
页面逻辑代码
布局所对应的Activity代码的编写。
在 MainActivity.java
中声明控件:
ViewPager mainVp; //显示标题,很多个文本组成
PagerSlidingTabStrip tabStrip; //显示fragment
ImageView addIv;
之后在 OnCreate
中通过 findViewById
找到这些控件:
mainVp=findViewById(R.id.main_vp);
tabStrip=findViewById(R.id.main_tabstrip);
addIv=findViewById(R.id.main_iv_add);
addIv需要实现点击跳转到下级页面中,这里让整个Activity实现接口 OnClickListener
,然后重写点击事件。
页面频道订阅逻辑编写
新建一个activity AddItemActivity
,在布局 activity_add_item.xml
进行布局,整体为线性布局,上面的频道订阅显示为相对布局,中间一条分割线,下面是一个ListView:
接下来写对应的Item的布局,在Layout文件下创建一个新的布局 item_add_lv.xml
,这个左右结构,选择相对布局。
在 MainActivity
中需要实现点击加号按钮实现响应事件,之后跳转到刚才的 AddItemActivity
//实现点击加号从MainActivity跳转到AddItemActivity界面
public void onClick(View v) {
switch (v.getId()){
case R.id.main_iv_add:
Intent intent = new Intent(MainActivity.this, AddItemActivity.class);
startActivity(intent);
break;
}
}
在 AddItemActivity
中添加控件声明和寻找控件:
//声明控件
ImageView backIv;
ListView addLv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_item);
//查找控件
backIv=findViewById(R.id.add_iv_back);
addLv=findViewById(R.id.add_lv);
}
backIv
要实现返回上一级的功能,在这里依然是实现 OnClickListener
的接口,然后重写 onClick
方法。首先给 backIv
设置监听:
backIv.setOnClickListener(this); //添加点击事件的监听
因为当前的Activity实现了 OnClickListener
这个接口,所以这个Activity的对象就是这个接口的对象,要向backIv中传入 OnClickListener
的接口对象,就可以直接传入他的实现类Activity的对象,所以这里传 this
即可。
backIv
被点击之后的事件可以在 onClick
方法中执行:
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.add_iv_back:
finish(); //销毁当前的Activity,返回上一级界面
break;
}
}
此时 backIv
的操作已写完。
接下来就是写对 addLv
的操作,它是用来显示所有的频道信息, ListView addLv
中的数据源应该是之前写的 TypeBean
, TypeBean
中就封装了title、URL和是否显示。
//数据源
List<TypeBean>mDatas;
由于信息是会改变的,当这次选中的频道,我们希望下次进入之后还会保持上一次选中的结果,所以这里需要本地存储,这里选择的本地存储为数据库。
新建一个关于数据库的包 db
,然后创建一个数据库的管理类 DBOpenHelper
,使其继承于 SQLiteOpenHelper
,
public class DBOpenHelper extends SQLiteOpenHelper {
public DBOpenHelper(@Nullable Context context) {
super(context, "info.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql="create table itype(id integer primary key,title varchar(10) unique not null,url text not null,isshow varchar(10) not null)";
db.execSQL(sql);
String inserSql="insert into itype values(?,?,?,?)";
db.execSQL(inserSql,new Object[]{1,"头条", NewsURL.headline_url,"true"});
db.execSQL(inserSql,new Object[]{2,"社会",NewsURL.society_url,"true"});
db.execSQL(inserSql,new Object[]{3,"国内",NewsURL.home_url,"true"});
db.execSQL(inserSql,new Object[]{4,"国际",NewsURL.entertainment_url,"true"});
db.execSQL(inserSql,new Object[]{5,"娱乐",NewsURL.entertainment_url,"true"});
db.execSQL(inserSql,new Object[]{6,"体育",NewsURL.sport_url,"false"});
db.execSQL(inserSql,new Object[]{7,"军事",NewsURL.military_url,"false"});
db.execSQL(inserSql,new Object[]{8,"科技",NewsURL.science_url,"false"});
db.execSQL(inserSql,new Object[]{9,"财经",NewsURL.fiance_url,"false"});
db.execSQL(inserSql,new Object[]{10,"时尚",NewsURL.fashion_url,"false"});
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
其中true或false决定了该栏目是显示还是隐藏。
接下来写一个获取数据库中全部信息的集合。在数据库的包 db
中新建一个数据库的管理类 DBManager
,在这里写一个关于数据库声明的函数,再添加一个获取数据库中全部类型的list集合:
public class DBManager {
public static SQLiteDatabase database;
public static void initDB(Context context){
DBOpenHelper helper=new DBOpenHelper(context);
database=helper.getWritableDatabase();
}
/*获取数据库中全部行的内容,存储到集合当中*/
public static List<TypeBean>getAllTypeList(){
List<TypeBean>list=new ArrayList<>();
Cursor cursor=database.query("itype",null,null,null,null,null,null);
while (cursor.moveToNext()){
int id = cursor.getInt(cursor.getColumnIndex("id"));
String title = cursor.getString(cursor.getColumnIndex("title"));
String url = cursor.getString(cursor.getColumnIndex("url"));
String showstr = cursor.getString(cursor.getColumnIndex("isshow"));
Boolean isshow = Boolean.valueOf(showstr);
TypeBean typeBean=new TypeBean(id,title,url,isshow);
list.add(typeBean);
}
return list;
}
}
将数据库的声明 database
放到全局变量中,在 UniteApp.java
中添加:
DBManager.initDB(this); //声明全局的数据库对象
在 AddItemActivity
需要的就是数据库中的所有信息,这里可以直接调用 DBManager
方法来获取:
mDatas= DBManager.getAllTypeList();
此时数据源就有了,接下来要创建适配器对象,写一下ListView的适配器对象:新建一个java class AddItemAdapter
,让它继承于 BaseAdapter
,重新里面的四个方法:
public class AddItemAdapter extends BaseAdapter {
Context context;
List<TypeBean>mDatas;
//通过构造方法将上面两个内容传递进来
public AddItemAdapter(Context context, List<TypeBean> mDatas) {
this.context = context;
this.mDatas = mDatas;
}
@Override
public int getCount() {
return mDatas.size(); //返回一共显示的字段
}
@Override
public Object getItem(int position) {
return mDatas.get(position); //返回当前位置的数据源
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView= LayoutInflater.from(context).inflate(R.layout.item_add_lv,null);
//初始化convertView当中的控件
TextView nameTv=convertView.findViewById(R.id.item_add_tv);
final ImageView iv=convertView.findViewById(R.id.item_add_iv);
//获取指定位置的数据
final TypeBean typeBean=mDatas.get(position); //获取到当前位置的数据源
nameTv.setText(typeBean.getTitle());
//当isShow()设置为true的时候,对应的后面为对号,当isShow()为false的时候,对应的后面为加号,就是不选中
if (typeBean.isShow()){
iv.setImageResource(R.mipmap.subscribe_checked);
}else {
iv.setImageResource(R.mipmap.subscribe_unchecked);
}
//为了避免所有的选项都没有选中ViewPager没有东西可以显示,默认前两项是选中的
if (position == 0 || position == 1) {
iv.setVisibility(View.INVISIBLE);
}else {
iv.setVisibility(View.VISIBLE);
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
typeBean.setShow(!typeBean.isShow()); //改变选中的状态
if (typeBean.isShow()) {
iv.setImageResource(R.mipmap.subscribe_checked);
} else {
iv.setImageResource(R.mipmap.subscribe_unchecked);
}
}
});
}
return convertView;
}
}
接下来就在 AddItemActivity
中创建适配器对象和设置适配器:
//创建适配器对象
adapter = new AddItemAdapter(this, mDatas);
//设置适配器
addLv.setAdapter(adapter);
至此这个界面完成。
每次选中想要订阅的频道,想要在下次打开app的时候还是保留上次选中的频道,还需要把点击的内容进行提交。
onPause
是Activity中的一个生命周期,表示失去焦点时调用的方法,Activity一共有七个生命周期: onCreate
(创建了) 、 onStart
(启动) 、 onResume
(获取焦点)、 onPause
(失去焦点)、 onStop
(停止) 、 onDestroy
(销毁)、 onRestart
(重新启动)。
当一个Activity跳转到另一个界面,该Activity就会处于先onPause(失去焦点),再onStop(停止) 的阶段,并没有销毁,因为它依然在栈当中存在着,当返回到这个Activity界面之后,首先会执行onRestart(重新启动),不会执行创建,再执行onStart(启动)
所以 onRestart
(重新启动)是失去焦点但是并没有销毁,重新获得焦点之后所执行的生命周期。
这些生命周期都不需要我们自己调用,Android底层会根据Activity的状态自动调用
所以这里可以用生命周期的状态来决定,这里一旦Activity的失去焦点,说明它已经被销毁(这里就是被销毁掉了,因为没有做跳转界面的操作),这里可以将本次选中的内容进行保留、提交。
所以这里可以写一下对于数据修改的方法。
在 DBOpenHelper
中添加:
/*修改数据库当中信息的选中记录*/
public static void updateTypeList(List<TypeBean>typeList){
for (int i = 0; i < typeList.size(); i++) {
TypeBean typeBean = typeList.get(i);
String title = typeBean.getTitle();
ContentValues values = new ContentValues();
values.put("isshow",String.valueOf(typeBean.isShow()));
database.update("itype",values,"title=?",new String[]{title}); //在主线程中直接修改数据库(该数据库数据量比较少可以这样做)
}
}
之后在 AddItemActivity
中添加如下代码,当该页面失去焦点时候,修改数据库:
@Override
protected void onPause() {
super.onPause();
DBManager.updateTypeList(mDatas);
}
运行程序之前先把之前安装的app卸载掉,因为数据库只有在刚装的时候才会执行onCreate方法,如果是更新的话就不再执行onCreate方法了,而是执行onUpdate方法。
效果如下:
这里先不管ViewPager页数是否改变,可以看到可以正常返回上一级页面,在下次打开app的时候还会是保留上次选中的频道,说明 AddItemActivity
对于数据库的操作是正确的。
ViewPager页数的改变是获取数据库的信息,下面介绍。