Android实战开发——News

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:

Android实战开发——News

接下来写对应的Item的布局,在Layout文件下创建一个新的布局 item_add_lv.xml ,这个左右结构,选择相对布局。

Android实战开发——News

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 中的数据源应该是之前写的 TypeBeanTypeBean 中就封装了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方法。
效果如下:

Android实战开发——News

这里先不管ViewPager页数是否改变,可以看到可以正常返回上一级页面,在下次打开app的时候还会是保留上次选中的频道,说明 AddItemActivity 对于数据库的操作是正确的。

ViewPager页数的改变是获取数据库的信息,下面介绍。

页面详细信息逻辑编写

Android实战开发——News

上一篇:ios修改导航栏上返回按钮上的文字,例如把back修改为返回


下一篇:react-native运行iOS真机、模拟器命令