zoukankan      html  css  js  c++  java
  • Android实战开发——News

    1.功能分析介绍

    知识点

    • ViewPager :页面的滑动
    • PagerSlidingTabStrip :第三方的自定义View,使得菜单栏和下面的页面产生联动的效果。
    • ListView 列表视图
    • WebView 控件:详情页面加载网址
    • 如何获取网络数据并解析展示
    • 数据库的增删改查:需要保留自定义的频道信息,当下一次进入该应用时候,会显示上一次保留的频道信息。

    使用第三方框架

    • Volley 框架:是网络加载数据的框架
    • Universal-image-loader 图片加载框架
    • PagerSlidingTabStrip 第三方定义view的使用

    逻辑分析

    • 首界面为 ViewPager ,上面为 PagerSlidingTabStrip ,两个控件可以相互影响,点击“+”,可以跳转到频道订阅界面。
    • 频道订阅界面,能够选择首界面显示的新闻类型,改变上一次选择内容返回上级页面时会改变首界面的显示内容,其中头条和社会是默认选项,不能改变。
    • 点击首界面列表中的每一条目,会跳转到详细页面,显示新闻的详细信息。

    具体代码的实现托管到了GitHub:https://github.com/ydd997/Android_news

    下面介绍重要的几个模块。

    2.页面布局绘制和接口分析

    导入所需要的包

    前期准备工作,把 PagerSlidingTabStrip 中的res和src中的相关文件导入: background_tab.xml 导入drawable中, attrs.xml 是关于自定义View PagerSlidingTabStrip 属性的xml文件,导入到values文件下,把 colors.xml 中的两条属性复制进入项目;,把 PagerSlidingTabStrip.java 放入view包(自己创建,存放自己写的View)下。

    把需要的图片放到res的mipmap-hdpi中,因为图片本身比较少

    直接第三方框架java包的导入资源:放到当前项目的build.gradle当中

    之后点击右上角的Sync,将数据整体格式化和下载。

    页面布局绘制

    activity_main中,整体是上下结构,选用 LinearLayout ,整体是垂直方向,

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:background="@color/white"
            >
    
            <com.example.news.view.PagerSlidingTabStrip
                android:id="@+id/main_tabstrip"
                android:layout_height="60dp"
                android:layout_width="0dp"
                android:layout_weight="1"
                app:pstsTabBackground="@color/white"
                app:pstsDividerColor="#ffffff"
                app:pstsIndicatorHeight="8dp"
                app:pstsUnderlineHeight="3dp"
                >
            </com.example.news.view.PagerSlidingTabStrip>
    
            <ImageView
                android:id="@+id/main_iv_add"
                android:layout_width="60dp"
                android:layout_height="60dp"
                android:scaleType="center"
                android:src="@mipmap/bar_img_subscribe"
                />
    
        </LinearLayout>
    
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="@color/black"
            />
    
        <androidx.viewpager.widget.ViewPager
            android:id="@+id/main_vp"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            >
        </androidx.viewpager.widget.ViewPager>
    
    </LinearLayout>
    

    不需要上面的标题栏,在styles中修改属性为 NoActionBar

    首页面的布局如下:

    首页面中的ViewPager布局至关重要,是一个ListView

    在Layout中创建一个ViewPager中所包含的Fragment布局 NewsInfoFragment ,在对应的布局文件 fragment_news_info 中绘制布局:整体就是一个ListView

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".frag.NewsInfoFragment">
    
        <!-- TODO: Update blank fragment layout -->
        <ListView
            android:id="@+id/newsfrag_lv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:dividerHeight="1dp"
            android:divider="@color/gray"
            />
    </FrameLayout>
    

    ListView中又涉及到每一个Item的布局,再绘制一个Layout item_newsfrag_lv.xml 作为ListView中item的布局:
    整体采用线性布局,设置三张图片,占比为1:1:1,第一张图片fitXY等比例放大,后两张图片centerCrop锁定长宽比缩放,裁剪显示。下面新闻来源和时间的信息采用相对布局,

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp">
    
    
        <TextView
            android:id="@+id/item_newsfrag_tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="国考经验交流会"
            android:textColor="@color/black"
            android:textSize="18sp"
            />
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginTop="10dp"
            >
            <ImageView
                android:id="@+id/item_news_iv1"
                android:layout_width="0dp"
                android:layout_height="120dp"
                android:layout_weight="1"
                android:src="@mipmap/bg_defualt_220x150"
                android:scaleType="fitXY"
                />
            <ImageView
                android:id="@+id/item_news_iv2"
                android:layout_width="0dp"
                android:layout_height="120dp"
                android:layout_weight="1"
                android:src="@mipmap/bg_defualt_220x150"
                android:scaleType="centerCrop"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="10dp"
                />
            <ImageView
                android:id="@+id/item_news_iv3"
                android:layout_width="0dp"
                android:layout_height="120dp"
                android:layout_weight="1"
                android:src="@mipmap/bg_defualt_220x150"
                android:scaleType="centerCrop"
                />
        </LinearLayout>
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            >
            <TextView
                android:id="@+id/item_news_tv_source"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="新浪"
                android:drawableLeft="@mipmap/topic_user_default"
                android:drawablePadding="20dp"
                android:gravity="center_vertical"
                android:textColor="@color/gray"
                android:textSize="14sp"
                />
            <TextView
                android:id="@+id/item_news_tv_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:text="2020-7-17 11:28:18"
                android:textColor="@color/gray"
                android:textSize="14sp"
                android:layout_alignBaseline="@id/item_news_tv_source"
                />
        </RelativeLayout>
    
    </LinearLayout>
    

    ListView中每一个Item的布局展示效果入下:

    数据来源和获取

    从聚合数据中,找到新闻头条,申请接口,

    数据的获取:

    接口的整理

    写一个能够将我们要访问的网址都存放的类,对该类进行统一的操作。

    创建一个新的包 bean ,在该包中创建一个新闻的URL类 NewsURL :(将这些接口放到统一的类中进行管理)

    package com.example.news.bean;
    
    public class NewsURL {
        //公共的key
        public static String key="46004cb8305704349056ee49ae3c5aca";
        public static String info_url="http://v.juhe.cn/toutiao/index?key="+key+"&type=";
        //头条
        public static String headline_url=info_url+"top";
        //社会
        public static String society_url=info_url+"shehui";
        //国内
        public static String home_url=info_url+"guonei";
        //国际
        public static String international_url=info_url+"guoji";
        //娱乐
        public static String entertainment_url=info_url+"yule";
        //体育
        public static String sport_url=info_url+"tiyu";
        //军事
        public static String military_url=info_url+"junshi";
        //科技
        public static String science_url=info_url+"keji";
        //财经
        public static String fiance_url=info_url+"caijing";
        //时尚
        public static String fashion_url=info_url+"shishang";
    }
    

    将这些接口放到统一的类中便于管理,这些接口还有所对应的名称,对应的名称会显示在ViewPager的上面,所以要将接口和名称绑定到一起,可以将接口和名称封装到同一个对象里。

    在bean中新建一个专门用于表示接口和其类型的类 TypeBean

    package com.example.news.bean;
    /*
    * 绑定接口名称和类型的类
    * */
    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.List;
    
    public class TypeBean implements Serializable {
        private int id; //进行数据库的存取
        private String title;
        private String url;
        private boolean isShow; //是否被选中
    
        //全参的构造方法
        public TypeBean(int id, String title, String url, boolean isShow) {
            this.id = id;
            this.title = title;
            this.url = url;
            this.isShow = isShow;
        }
    
        //空参的构造方法
        public TypeBean() {
        }
        
        //用到的set、get方法
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public boolean isShow() {
            return isShow;
        }
    
        public void setShow(boolean show) {
            isShow = show;
        }
    
        //内存存储
        public static List<TypeBean> getTypeList(){
            List<TypeBean>mDatas=new ArrayList<>();
            TypeBean tb1 = new TypeBean(1,"头条",NewsURL.headline_url,true);
            TypeBean tb2 = new TypeBean(2,"社会",NewsURL.society_url,true);
            TypeBean tb3 = new TypeBean(3,"国内",NewsURL.home_url,true);
            TypeBean tb4 = new TypeBean(4,"国际",NewsURL.international_url,true);
            TypeBean tb5 = new TypeBean(5,"娱乐",NewsURL.entertainment_url,true);
            TypeBean tb6 = new TypeBean(6,"体育",NewsURL.sport_url,true);
            TypeBean tb7 = new TypeBean(7,"军事",NewsURL.military_url,true);
            TypeBean tb8 = new TypeBean(8,"科技",NewsURL.science_url,true);
            TypeBean tb9 = new TypeBean(9,"财经",NewsURL.fiance_url,true);
            TypeBean tb10 = new TypeBean(10,"时尚",NewsURL.fashion_url,true);
            mDatas.add(tb1);
            mDatas.add(tb2);
            mDatas.add(tb3);
            mDatas.add(tb4);
            mDatas.add(tb5);
            mDatas.add(tb6);
            mDatas.add(tb7);
            mDatas.add(tb8);
            mDatas.add(tb9);
            mDatas.add(tb10);
            return  mDatas;
        }
    }
    

    现在就将想要获取的网址和他们的标题都封装在同一对象里,并且将所有的对象都存储在集合里,可以通过操作这个集合来操作相关信息。

    下面将获取到的数据都展现在ViewPager中,并且可以上下滑动,产生上下联动的效果。

    3.页面逻辑代码

    完成布局和所对应的Activity代码的编写

    MainActivity.java 中声明控件:

    ViewPager mainVp;  
    PagerSlidingTabStrip tabStrip;  //显示Title
    ImageView addIv; //点击跳转到下级页面中
    

    之后在 OnCreate 中通过 findViewById 找到这些控件:

    mainVp=findViewById(R.id.main_vp);
    tabStrip=findViewById(R.id.main_tabstrip);
    addIv=findViewById(R.id.main_iv_add);
    

    ViewPagerPagerSlidingTabStrip 是相互对应的, ViewPager 中所包含Fragment,把Fragment放到一个集合中,集合类型为Fragment;网址以及标题都存储在TypeBean当中,可以把选中的TypeBean放到一个集合中:

    List<Fragment>fragmentList; // ViewPager 所显示的内容
    List<TypeBean>selectTypeList; //所选中的类型的集合
    

    创建一个初始化界面的函数initPager():

        private void initPager() {
            /*初始化页面的函数*/
            //List<TypeBean> typeList = TypeBean.getTypeList(); //获取全部
    
            List<TypeBean> typeList = DBManager.getSelectTypeList(); //获取选中的栏目的页面
    
            selectTypeList.addAll(typeList);
            for (int i = 0; i < selectTypeList.size(); i++) {
                TypeBean typeBean = selectTypeList.get(i); //得到每一个栏目的信息对象
                NewsInfoFragment infoFragment = new NewsInfoFragment();
                //向Fragment当中传值
                Bundle bundle = new Bundle();
                bundle.putSerializable("type",typeBean);
                infoFragment.setArguments(bundle);
                fragmentList.add(infoFragment);
            }
        }
    

    此时ViewPager的数据源已经完成,接下来写ViewPager对应的Adapter

    创建一个新的Adapter 为 NewsInfoAdapter 。ViewPager对应的类型是Fragment类型,

    ViewPager跳转到其他页面中,返回时会造成页面数据的变化,这里要继承 FragmentStatePagerAdapter

    public class NewsInfoAdapter extends FragmentStatePagerAdapter {
        Context context;
        List<Fragment>fragmentList; //viewpager每个页面页面展示的fragment集合
        List<TypeBean>typeBeanList; //PagerSlidingTabStrip所展示的标题
    
        //构造方法
        public NewsInfoAdapter(FragmentManager fm,Context context,List<Fragment>fragmentList,List<TypeBean>typeBeanList) {
            super(fm);
            this.context=context;
            this.fragmentList=fragmentList;
            this.typeBeanList=typeBeanList;
        }
    
        @Override
        public int getItemPosition(@NonNull Object object) {
            return PagerAdapter.POSITION_NONE;
        }
        
        //要求重写的函数
        @Override
        public Fragment getItem(int position) {
            return fragmentList.get(position); //返回指定位置的fragment
        }
        
        //要求重写的函数
        @Override
        public int getCount() {
            return fragmentList.size(); //返回要加载的页数
        }
    
    
        //返回指定位置的标题(如果ViewPager和其他控件有互相位置的关联关系需要重写这个方法)
        @Nullable
        @Override
        public CharSequence getPageTitle(int position) {
            TypeBean typeBean = typeBeanList.get(position);
            String title = typeBean.getTitle();
            return title;  //返回指定位置的标题
        }
    }
    

    回到MainActivity中继续设置适配器:

            //创建适配器对象
            adapter = new NewsInfoAdapter(getSupportFragmentManager(), this, fragmentList, selectTypeList);
            //设置适配器
            mainVp.setAdapter(adapter);
            //关联TapStrip和ViewPager
            tabStrip.setViewPager(mainVp);
    

    到此TapStrip和ViewPager关联完成:将ViewPager所用到的Fragment创建出来,把网址和标题传入到Fragment当中,联网是在Fragment中联网的(这里不进行操作)。把Fragment中的集合传入到Adapter当中,给ViewPager设置适配器,使得TapStrip和ViewPager相互关联,这就是MainActivity目前写到的代码。

    ListView展示的数据源:新建一个java类 InfoBean

    把测试得到的数据复制,然后格式化生成一个Bean类,

    由于涉及到使用 Universal-image-loader 图片加载框架来加载图片,这里先创建一个类 UnitApp 继承自Application,这是我们自定义的Application类,在一个项目工程中,他的对象是唯一的。

    这里定义一个初始化ImageLoader的方法 initImageLoader ,要自己写。

    public class UniteApp extends Application {
    
        @Override
        public void onCreate() {
            super.onCreate();
    
            initImageLoader(getApplicationContext()); //初始化图片加载框架ImageLoader
        }
    
        //初始化图片加载框架ImageLoader
        private void initImageLoader(Context context) {
            //ImageLoader的设置参数
            ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(context)
                    .threadPriority(Thread.MAX_PRIORITY).denyCacheImageMultipleSizesInMemory()
                    .diskCacheFileNameGenerator(new Md5FileNameGenerator())
                    .tasksProcessingOrder(QueueProcessingType.LIFO)
                    .writeDebugLogs()
                    .build();
            ImageLoader.getInstance().init(configuration);
        }
    }
    

    初始化图片加载框架ImageLoader中:设置了线程的优先级是最高级;使用一定数量的缓存;使用MD%的磁盘存储命名方式;使用LIFO(最近最少使用放后面)排队方式;打印log日志。

    这里要对自定义的Application进行声明:

    创建一个ListView的适配器 InfoitemAdapter ,继承自 BaseAdapter

    其中获取三张图片的地址分别为pic1、pic2、pic3,如果有地址存在,就显示,如果没有地址就不显示,还不让他占地方(View.GONE)

    /*每一个Fragment当中的ListView的适配器*/
    public class InfoitemAdapter extends BaseAdapter {
    
        Context context; //表示ListView所在的Activity
        List<InfoBean.ResultBean.DataBean> mDatas; //数据源
        ImageLoader imageLoader;
        DisplayImageOptions options; //图片加载配置信息
    
        public InfoitemAdapter(Context context, List<InfoBean.ResultBean.DataBean> mDatas) {
            this.context = context;
            this.mDatas = mDatas;
            imageLoader=ImageLoader.getInstance();
            options=new DisplayImageOptions.Builder()
                    .showImageOnLoading(null) //正在加载中什么都不展示
                    .showImageForEmptyUri(null) //空字符串显示空
                    .showImageOnFail(null)  //加载失败显示空
                    .cacheInMemory(true).cacheOnDisk(true).considerExifParams(true) //使用缓存、磁盘存储
                    .bitmapConfig(Bitmap.Config.RGB_565).build(); //存储的图片类型是565
        }
    
        @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) {
            ViewHolder holder=null;
            //1.判断内存中是否有复用的view
            if(convertView==null){
                //2.将布局转换成新的view进行使用
                convertView= LayoutInflater.from(context).inflate(R.layout.item_newsfrag_lv,null);
                holder=new ViewHolder(convertView);
                convertView.setTag(holder);
            }else {
                holder= (ViewHolder) convertView.getTag();
            }
            //获取指定位置的数据源
            InfoBean.ResultBean.DataBean dataBean = mDatas.get(position);
            holder.titleTv.setText(dataBean.getTitle());
            holder.sourceTv.setText(dataBean.getAuthor_name());
            holder.timeTv.setText(dataBean.getDate());
    
            //获取三张图片的地址
            String pic1 = dataBean.getThumbnail_pic_s();
            String pic2 = dataBean.getThumbnail_pic_s02();
            String pic3 = dataBean.getThumbnail_pic_s03();
    
            if (TextUtils.isEmpty(pic1)) {
                holder.iv1.setVisibility(View.GONE); //如果为空就不显示也不占地方
            }else {
                holder.iv1.setVisibility(View.VISIBLE); //不为空就显示
                imageLoader.displayImage(pic1,holder.iv1,options);
            }
    
            if (TextUtils.isEmpty(pic2)) {
                holder.iv2.setVisibility(View.GONE);
            }else {
                holder.iv2.setVisibility(View.VISIBLE);
                imageLoader.displayImage(pic2,holder.iv2,options);
            }
    
            if (TextUtils.isEmpty(pic3)) {
                holder.iv3.setVisibility(View.GONE);
            }else {
                holder.iv3.setVisibility(View.VISIBLE);
                imageLoader.displayImage(pic3,holder.iv3,options);
            }
    
            return convertView;
        }
    
        //定义控件
        class ViewHolder{
            TextView titleTv,sourceTv,timeTv;
            ImageView iv1,iv2,iv3;
            //初始化item控件
            public ViewHolder(View view){
                titleTv=view.findViewById(R.id.item_newsfrag_tv_title);
                sourceTv=view.findViewById(R.id.item_news_tv_source);
                timeTv=view.findViewById(R.id.item_news_tv_time);
                iv1=view.findViewById(R.id.item_news_iv1);
                iv2=view.findViewById(R.id.item_news_iv2);
                iv3=view.findViewById(R.id.item_news_iv3);
            }
        }
    }
    
    

    到此为止,每一个Item中的控件都显示完成,titleTv、sourceTv和timeTv通过setText设置了文本资料,ImageView通过ImageLoader来加载显示的内容,

    ListView的适配器 InfoitemAdapter 就写完了

    回到他所在的 NewsInfoFragment 中,设置Adapter对象,

    public class NewsInfoFragment {
        ListView infoLv;
        private String url;
        //listView的数据源
        List<InfoBean.ResultBean.DataBean> mData;
        private InfoitemAdapter adapter;
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View view=inflater.inflate(R.layout.fragment_news_info, container, false);
            infoLv=view.findViewById(R.id.newsfrag_lv);
            //获取Activity传递的数据
            Bundle bundle = getArguments();
            TypeBean typeBean= (TypeBean) bundle.getSerializable("type");
            url = typeBean.getUrl();
    
            mData = new ArrayList<>();
            //创建ListView的适配器对象
    
            adapter = new InfoitemAdapter(getActivity(), mData);
            infoLv.setAdapter(adapter);
    
            return view;
        }
    }
    

    现在为止其实是并没有数据的,因为我们的网址并没有获取数据,我们的数据源是一个空的集合,只是一个集合但其实里面并没有数据,所以传入到adapter当中是什么都显示不出来的。

    我们使用第三方框架 Volley 来获取网络数据,获取相对应网址的信息,

    网络数据的加载

    在自定义的App UnitApp 中对于 Volley 进行声明:

    Volley 是如何获得网络请求的呢?通常是将 封装到一个BaseFragment当中。创建一个新的java类 BaseFragment 继承自 Fragment ,将网络请求的过程写在这个Fragment当中。

    public class BaseFragment extends Fragment implements Response.Listener<String>, Response.ErrorListener {
        public void loadDate(String url){
            //创建网络请求对象 StringRequest 
            StringRequest request=new StringRequest(url,this,this);
    
            //将请求添加到请求队列中
            UniteApp.getHttpQueue().add(request);
        }
    
        @Override
        public void onErrorResponse(VolleyError error) {
            //获取网络请求失败时,会回调的函数
        }
    
        @Override
        public void onResponse(String response) {
            //获取网络请求成功时,会回调的函数
        }
    }
    

    回到 NewsInfoFragment 中就可以加载网络数据了:

    此时 NewsInfoFragment 中的代码如下:

    public class NewsInfoFragment extends BaseFragment {
        ListView infoLv;
        private String url;
        //listView的数据源
        List<InfoBean.ResultBean.DataBean> mData;
        private InfoitemAdapter adapter;
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View view=inflater.inflate(R.layout.fragment_news_info, container, false);
            infoLv=view.findViewById(R.id.newsfrag_lv);
            //获取Activity传递的数据
            Bundle bundle = getArguments();
            TypeBean typeBean= (TypeBean) bundle.getSerializable("type");
            url = typeBean.getUrl();
    
            mData = new ArrayList<>();
            //创建ListView的适配器对象
    
            adapter = new InfoitemAdapter(getActivity(), mData);
            infoLv.setAdapter(adapter);
    
            loadDate(url);
            return view;
        }
    
        //获取数据成功时,会调用的函数
        @Override
        public void onResponse(String response) {
            InfoBean infoBean = new Gson().fromJson(response, InfoBean.class);
            List<InfoBean.ResultBean.DataBean> list = infoBean.getResult().getData();
            //添加到数据源中
            mData.addAll(list);
            //提示adapter数据源发送变化了,更新数据
            adapter.notifyDataSetChanged();
        }
    
        //获取数据失败时,会调用的函数
        @Override
        public void onErrorResponse(VolleyError error) {
    
        }
    }
    

    这里用到了网络数据,需要将网络请求添加上:

    <uses-permission android:name="android.permission.INTERNET" />
    

    Android9以上的手机运行连不起网,需要加一个

    android:usesCleartextTraffic="true"
    

    此时运行遇到的问题:

    在 Android 6.0 中,我们取消了对 Apache HTTP 客户端的支持。 从 Android 9 开始,默认情况下该内容库已从 bootclasspath 中移除且不可用于应用。

    要继续使用 Apache HTTP 客户端,以 Android 9 及更高版本为目标的应用可以向其 AndroidManifest.xmlapplication 节点下 添加以下内容:

    <uses-library 
        android:name="org.apache.http.legacy" 
        android:required="false"/>
    

    此时运行可以看到新闻界面,这里忘记截图了o(╥﹏╥)o

    现在并不知道浏览的界面具体对应上面哪一个标题,点击上面的菜单栏时候字体是没有变化的。这里希望点击上面的标题时,对应的标题颜色和字体发生变化。

    PagerSlidingTabStrip 这个第三方的自定义View中定义几个变量:

    	//正在被选中的位置
    	private int selectionPosition;
    	//设置被选文字的大小
    	private int selectionTextSize=18;
    	//设置被选中的文字颜色为蓝色
    	private int selectionTextColor= Color.BLUE;
    

    然后将被选中的位置 selectionPosition 进行赋值,

    PagerSlidingTabStrip 中的 PageListener下的onPageSelected添加正在被选中的位置:

    updateTabStyles() 中可以对文字的颜色、大小进行设置,添加代码实现判断这个位置是否为选中位置,如果是选中位置,就改变文字颜色和文字大小:

    再次执行 updateTabStyles() 函数:

    现在运行可以发现当我们滑动的时候,上面的条目可以对的上而且文字和颜色都会发生变化。

    4.页面频道订阅逻辑编写

    需要实现:点击加号跳转到“频道订阅界面”,给“频道订阅”中的每一条设置点击事件,点击会显示是否被订阅,并将数据存储到数据库中。

    新建一个activity AddItemActivity ,在布局 activity_add_item.xml 进行布局,整体为线性布局,上面的频道订阅显示为相对布局,中间一条分割线,下面是一个ListView:

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

    MainActivity 中需要实现点击加号按钮实现响应事件,之后跳转到刚才的 AddItemActivity ,这里让整个Activity实现接口 OnClickListener ,然后重写点击事件。:(这里通过事件源所在类实现

        //实现点击加号从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方法。
    效果如下:

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

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

    5.页面详细信息逻辑编写

    需要实现:根数数据库内容的变化来改变ViewPager和PagerSlidingTabStrip的显示;点击ListView中的每一项跳转到相应的网址当中。

    需要将选中的条目放到一个集合中,在 DBManager 中添加如下代码:

        /*获取所有要求显示内容的集合*/
        public static  List<TypeBean>getSelectTypeList(){
            List<TypeBean>list = new ArrayList<>();
            Cursor cursor = database.query("itype", null, "isshow='true'", 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"));
                TypeBean bean = new TypeBean(id, title, url, true);
                list.add(bean);
            }
            return list;
        }
    

    更改一下 MainActivity 中的代码,原来是获取TypeBean中的getTypeList()函数来显示全部页面,现在需要获取选中的栏目的页面,就来获取 DBManager中的getSelectTypeList()函数:

    现在运行界面如下:(现在还并没有引起后面选中和前面对应的改变,只是显示的是后台选中的那几个栏目,)

    一旦要改变ViewPager中的数量,就要对其Adapter——> NewsInfoAdapter 进行操作,需要在这里写一个函数 getItemPosition

        @Override
        public int getItemPosition(@NonNull Object object) {
            return PagerAdapter.POSITION_NONE;
        }
    
    

    页数发生变化的情况是:当我们在频道界面改变订阅的频道时候,返回ViewPager的时候页面会发生变化。这里首先会执行 onRestart(重新启动),再执行 onStart (启动)。

    在这里执行 onRestart 方法:

        protected void onRestart() {
            super.onRestart();
    
            //先清空ViewPager的数据源,再清空选中列表的数据源
            fragmentList.clear(); //首先将fragment整体清空
            selectTypeList.clear(); //将选中的列表清空
    
            initPager(); //重新加载ViewPager的显示页
    
            //提示上下都更新数据
            adapter.notifyDataSetChanged(); //通知adapter更新
            tabStrip.notifyDataSetChanged(); //通知上面的PagerSlidingTab更新
        }
    

    现在的效果如下:

    现在就实现了“频道订阅”会改变ViewPager所显示频道的功能。

    现在还剩最后一个功能没有实现:就是点击ListView中的每一项跳转到相应的网址当中。

    这里给出了直接的URL:

    可以将这个网址直接放置到Webview当中展示。

    新建一个package add (关于添加频道的包)把AddItemActivity和AddItemAdapter放进去(为了更加清晰明了)

    直接新建一个Activity DescActivity ,布局如下比较简单:

    DescActivity 中进行声明控件和找到控件:

    NewsInfoFragment 中添加一个函数 setListener() ,这个是设置ListView中每一项点击事件的函数

        /*设置ListView中每一项点击事件的函数*/
        private void setListener() {
            infoLv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    //获取指定位置的数据源
                    InfoBean.ResultBean.DataBean dataBean = mData.get(position);
                    String url = dataBean.getUrl();
                    Intent intent = new Intent(getActivity(), DescActivity.class); //从所在的Activity跳转到DescActivity当中
                    //在跳转的过程中进行传值
                    intent.putExtra("url",url);
                    startActivity(intent);
                }
            });
        }
    

    接下来就可以在 DescActivity 中获取所对应的URL,在 onCreate 中添加:

            url=getIntent().getStringExtra("url");
    

    现在网址有啦,需要进行加载,在 onCreate 中继续添加:

            //创建WebView的设置类,对属性进行设置
            WebSettings webSettings = descWeb.getSettings();
            webSettings.setJavaScriptEnabled(true); //设置页面支持js交互
            webSettings.setUseWideViewPort(true);  //将图片调整到适合WebView页面的大小
            webSettings.setLoadWithOverviewMode(true); //缩放至屏幕大小
            webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);  //设置webview的缓存方式
            webSettings.setAllowFileAccess(true);  //设置可以访问文件
            webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持js打开新窗口
            webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
            webSettings.setDefaultTextEncodingName("UTF-8"); //设置编码格式
    
            //设置要加载的网址
            descWeb.loadUrl(url); //此时系统会默认用手机浏览器打开网址
    
            //为了直接通过webview直接打开网址,需要设置以下操作
            descWeb.setWebViewClient(new WebViewClient(){
                @Override
                public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
                    //使用webview要加载的URL
                    view.loadUrl(url);
                    return true;
                }
            });
    

    现在的效果如下:

    只是点击返回的时候直接返回上一级Activity而不是上一级页面,如果想要返回上一级页面的话,进行如下操作:

    重写 DescActivity 中的 onKeyDown 方法:

        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_BACK && descWeb.canGoBack()) {
                descWeb.goBack(); //返回上一级
                return true;
            }
            return super.onKeyDown(keyCode, event);
        }
    

    再次运行:

    到此实现基本功能。

    在values下的 strings.xml 可以更改应用名称:

    在mipmap中导入图标,接下来在 AndroidManifest.xml 中修改就可以啦!

  • 相关阅读:
    Java 引用传递和值传递
    jenkins 自动化部署 spring boot 项目(多图)
    Mybatis学习笔记,持续更新
    ubuntu 安装并远程连接redis
    ubuntu redis 集群安装,超简单多图细腻操作
    ubuntu16.04 的 使用笔记
    阿里云 ubuntu16.04 下 ftp 的快速应用(包罗疑难问题解决方案)
    k8s的#容器镜像
    kubectl命令出现【The connection to the server localhost:8080 was refused
    CIDR无类别域间路由
  • 原文地址:https://www.cnblogs.com/yangdd/p/13341275.html
Copyright © 2011-2022 走看看