Android 实践:做一款新闻 APP

跟代码相关的工作,大多唯手熟尔,所以这里花了点时间做了款简易版的新闻 APP,虽然都是些基础的内容,不过还是可以加深自己对部分代码的理解。至少,可以加深自己的记忆


步骤


  1. 依赖库
  2. 网络请求
  3. 网络解析
  4. 界面布局
  5. 最后
  6. 运行界面
  7. 运行GIF
  8. 完整代码下载地址(github)

依赖库


过程中需要用到一些开源依赖库文件,先在 build.grade 中声明:

    compile 'com.google.code.gson:gson:2.8.0'       //网络解析
    compile 'com.squareup.okhttp3:okhttp:3.7.0'     //网络请求
    compile 'com.github.bumptech.glide:glide:3.8.0' //图片加载
    compile 'com.android.support:design:24.2.1'     //Material Design中用到的依赖库
    compile 'de.hdodenhof:circleimageview:2.1.0'    //显示圆形图片

网络请求


在包下创建一个文件夹 util 用来存放工具类,创建文件 HttpUtil.class 用来请求数据:

public class HttpUtil {
    public static void sendOkHttpRequest(String address, okhttp3.Callback callback){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(address).build();
        client.newCall(request).enqueue(callback);
    }
}

这里用到的是 okhttp3.Callback 的回调接口,结果会返回到 callback 的回调函数中,后面会进行处理


网络解析


我们先从数据解析开始,毕竟这才是这个小项目的重点。这次项目使用的数据来源是天行数据(http://www.tianapi.com/ )的新闻资讯 API ,先看 API 的说明:

新闻资讯 API

可以看到返回数据为 JSON, 默认返回 10 条参数。请求地址为:

这里写图片描述

其中, APIKEY 需要用个人的 API KEY 代替,可以在个人中心中看到,其他的请求地址也是大同小异

这里写图片描述

JSON 返回示例:

这里写图片描述

还有错误返回码,用来判断返回数据的异常情况:

这里写图片描述

根据 gson 的返回示例,我们可以写出对应的实体类文件,通过 gson 将返回数据转化为对应的类型。先创建一个 gson 文件夹存放实体类文件。

在 gson 文件夹下创建 New.class 文件:

public class News {
    @SerializedName("ctime")
    public String time;

    public String title;

    public String description;

    public String picUrl;

    public String url;

}

创建 NewsList.class 文件:

public class NewsList {

    public int code;

    public String msg;

    @SerializedName("newslist")
    public List<News> newsList ;

}

至此,我们就已经创建好了与返回数据对应的实体类。

在 util 文件夹下创建文件 Utility.class 文件:

public class Utility {
    public static NewsList parseJsonWithGson(final String requestText){
        Gson gson = new Gson();
        return gson.fromJson(requestText, NewsList.class);
    }

}

将请求得到的数据解析为 NewList 实体类对象。现在网络请求和解析都准备好了,就开始界面文件了


界面布局


修改 values 目录下的 styles.xml 文件:

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimary</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

修改通知栏颜色和标题栏颜色一样,是处于视觉统一的原因,也可以不修改(非必须)


主要采用的是 Material Design 的设计,整体布局采用的是滑动菜单,主界面内容为 ToolBar 和 ListView(这里为了方便,就直接使用),侧边栏内容为 NavigationView


主界面:
因为要用 ToolBar 替代 ActionBar, 我们先修改 values 下面的 styles 文件,修改主题为:

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

在layout 下创建 nav_header 文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="180dp"
    android:background="@color/colorPrimary"
    android:padding="10dp">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/icon_image"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_centerInParent="true"
        android:src="@drawable/nav_icon" />

    <TextView
        android:id="@+id/username"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="https://github.com/lentitude"
        android:textColor="@color/color_White"
        android:textSize="14sp" />


    <TextView
        android:id="@+id/mail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/username"
        android:text="lentitude"
        android:textColor="@color/color_White"
        android:textSize="14sp" />

</RelativeLayout>

这里在头部文件中放置了一个CircleImageView,两个 TextView,没有什么理解难度


在 res 目录下创建 menu 文件夹,新建 nav_menu.xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <group android:checkableBehavior="single">
        <item
            android:id="@+id/nav_society"
            android:title="社会新闻" />
        <item
            android:id="@+id/nav_county"
            android:title="国内新闻" />
        <item
            android:id="@+id/nav_internation"
            android:title="国际新闻" />
        <item
            android:id="@+id/nav_fun"
            android:title="娱乐新闻" />
        <item
            android:id="@+id/nav_sport"
            android:title="体育新闻" />
        <item
            android:id="@+id/nav_nba"
            android:title="NBA新闻" />
        <item
            android:id="@+id/nav_football"
            android:title="足球新闻" />
        <item
            android:id="@+id/nav_technology"
            android:title="科技新闻" />
        <item
            android:id="@+id/nav_work"
            android:title="创业新闻" />
        <item
            android:id="@+id/nav_apple"
            android:title="苹果新闻" />
        <item
            android:id="@+id/nav_war"
            android:title="军事新闻" />
        <item
            android:id="@+id/nav_internet"
            android:title="移动互联" />
        <item
            android:id="@+id/nav_travel"
            android:title="旅游咨询" />
        <item
            android:id="@+id/nav_health"
            android:title="健康知识" />
        <item
            android:id="@+id/nav_strange"
            android:title="奇闻异事" />
        <item
            android:id="@+id/nav_looker"
            android:title="美女图片" />
        <item
            android:id="@+id/nav_vr"
            android:title="VR科技" />
        <item
            android:id="@+id/nav_it"
            android:title="IT资讯" />


    </group>
</menu>

这里创建了若干个 ITEM 子项,只有 title,没有 icon,大家可以自行放置

主界面 activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <android.support.v7.widget.Toolbar
                android:id="@+id/tool_bar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:contentInsetStart="0dp"
                app:titleTextColor="@color/color_White"
                android:background="@color/colorPrimary"
                />


        </android.support.design.widget.AppBarLayout>


        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/swipe_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <ListView
                android:id="@+id/list_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:divider="@color/color_Background"
                android:dividerHeight="1dp"

                />


        </android.support.v4.widget.SwipeRefreshLayout>

    </android.support.design.widget.CoordinatorLayout>


    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/nav_header"
        app:menu="@menu/nav_menu"
        />


</android.support.v4.widget.DrawerLayout>

因为是一步到位,所以……大家最好之前用过使用过相同的布局设计(比如:第一行代码)

DrawerLayout 中有两个直接子布局文件:
1. CoordinatorLayout:一种 FrameLayout, 作为显示主界面内容的最外层布局
2. NavigationView:作为显示侧边栏的最外层布局,不过已经封装好了,可以直接通过 app:headerLayout 和 app:menu 属性引用之前我们已经写好的 头部和菜单布局文件

CoordinatorLayout 中有两个直接子布局文件:
1. AppBarLayout :通过 AppBarLayout 属性,可以将 ToolBar 和 ListView 分隔开,可以对滚动事件进行响应,实现 Material 效果
2. SwipeRefreshLayout:用来刷新 ListView 中的内容

创建 list_view_item.xml 文件,设计 ListView 的子项布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:background="@color/color_White">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp">

        <ImageView
            android:id="@+id/title_pic"
            android:layout_width="80dp"
            android:layout_height="60dp"
            android:layout_centerVertical="true"
            android:layout_alignParentRight="true"
            android:scaleType="centerCrop"/>

        <TextView
            android:id="@+id/title_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="16sp"
            android:layout_marginRight="10dp"
            android:layout_alignTop="@+id/title_pic"
            android:layout_alignParentLeft="true"
            android:layout_toLeftOf="@+id/title_pic"
            />

        <TextView
            android:id="@+id/descr_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="8sp"
            android:layout_marginRight="10dp"
            android:layout_alignBottom="@+id/title_pic"
            android:layout_alignParentLeft="true"
            />

    </RelativeLayout>


</RelativeLayout>

子项布局内包含 3 个控件,ImageView 显示返回的图片,TextView 显示返回的标题和出处

创建一个 Title.class类:

public class Title {
    private String title;
    private String descr;
    private String imageUrl;
    private String uri;

    public Title(String title,String descr, String imageUrl, String uri){
        this.title = title;
        this.imageUrl = imageUrl;
        this.descr = descr;
        this.uri = uri;
    }

    public String getTitle() {
        return title;
    }

    public String getImageUrl() {
        return imageUrl;
    }

    public String getDescr() {
        return descr;
    }

    public String getUri() {
        return uri;
    }
}

这里之所以除了 标题,出处,图片显示在 ListViw 中,uri 传入另一个布局,显示内容文件

接下来就是 TitleAdapter.class

public class TitleAdapter extends ArrayAdapter<Title> {
    private int resourceId;

    public TitleAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull List<Title> objects) {
        super(context, resource, objects);
        resourceId = resource;
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        Title title = getItem(position);
        View view;
        ViewHolder viewHolder;
        /**
         * 缓存布局和实例,优化 listView
         */
        if (convertView == null){
            view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
            viewHolder = new ViewHolder();
            viewHolder.titleText = (TextView)view.findViewById(R.id.title_text);
            viewHolder.titlePic = (ImageView) view.findViewById(R.id.title_pic);
            viewHolder.titleDescr = (TextView)view.findViewById(R.id.descr_text);
            view.setTag(viewHolder);
        }else{
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();
        }

        Glide.with(getContext()).load(title.getImageUrl()).into(viewHolder.titlePic);
        viewHolder.titleText.setText(title.getTitle());
        viewHolder.titleDescr.setText(title.getDescr());

        return view;

    }

    public class ViewHolder{
        TextView titleText;
        TextView titleDescr;
        ImageView titlePic;
    }
}

这里还是一样的老套路,通过convertView 来缓存布局,通过类 ViewHolder 缓存控件实例,这样做,可以节省 50% 的效率,所以还是按照老套路走吧。

接下来就是 Activity 文件 MainActivity.class:

public class MainActivity extends AppCompatActivity {
    private static final int  ITEM_SOCIETY= 1;
    private static final int  ITEM_COUNTY= 2;
    private static final int  ITEM_INTERNATION= 3;
    private static final int  ITEM_FUN= 4;
    private static final int  ITEM_SPORT= 5;
    private static final int  ITEM_NBA= 6;
    private static final int  ITEM_FOOTBALL= 7;
    private static final int  ITEM_TECHNOLOGY= 8;
    private static final int  ITEM_WORK= 9;
    private static final int  ITEM_APPLE= 10;
    private static final int  ITEM_WAR= 11;
    private static final int  ITEM_INTERNET= 12;
    private static final int  ITEM_TREVAL= 13;
    private static final int  ITEM_HEALTH= 14;
    private static final int  ITEM_STRANGE= 15;
    private static final int  ITEM_LOOKER= 16;
    private static final int  ITEM_VR= 17;
    private static final int  ITEM_IT= 18;


    private List<Title> titleList = new ArrayList<Title>();
    private ListView listView;
    private TitleAdapter adapter;
    private NavigationView navigationView;
    private DrawerLayout drawerLayout;
    private SwipeRefreshLayout refreshLayout;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Toolbar toolbar = (Toolbar)findViewById(R.id.tool_bar);
        setSupportActionBar(toolbar);
        final ActionBar actionBar = getSupportActionBar();
        if (actionBar != null){
            actionBar.setDisplayHomeAsUpEnabled(true);
            actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
        }
        actionBar.setDisplayShowTitleEnabled(true);
        actionBar.setTitle("社会新闻");

        refreshLayout = (SwipeRefreshLayout)findViewById(R.id.swipe_layout);
        refreshLayout.setColorSchemeColors(getResources().getColor(R.color.colorPrimary));
        listView = (ListView)findViewById(R.id.list_view);
        adapter = new TitleAdapter(this,R.layout.list_view_item, titleList);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            Intent intent = new Intent(MainActivity.this, ContentActivity.class);
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Title title = titleList.get(position);
                intent.putExtra("title",actionBar.getTitle());
                intent.putExtra("uri",title.getUri());
                startActivity(intent);
            }
        });

        drawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout);
        navigationView = (NavigationView)findViewById(R.id.nav_view);
        navigationView.setCheckedItem(R.id.nav_society);
        navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                switch (item.getItemId()){
                    case R.id.nav_society:
                        handleCurrentPage("社会新闻",ITEM_SOCIETY);
                        break;
                    case R.id.nav_county:
                        handleCurrentPage("国内新闻",ITEM_COUNTY);
                        break;
                    case R.id.nav_internation:
                        handleCurrentPage("国际新闻",ITEM_INTERNATION);
                        break;
                    case R.id.nav_fun:
                        handleCurrentPage("娱乐新闻",ITEM_FUN);
                        break;
                    case R.id.nav_sport:
                        handleCurrentPage("体育新闻",ITEM_SPORT);
                        break;
                    case R.id.nav_nba:
                        handleCurrentPage("NBA新闻",ITEM_NBA);
                        break;
                    case R.id.nav_football:
                        handleCurrentPage("足球新闻",ITEM_FOOTBALL);
                        break;
                    case R.id.nav_technology:
                        handleCurrentPage("科技新闻",ITEM_TECHNOLOGY);
                        break;
                    case R.id.nav_work:
                        handleCurrentPage("创业新闻",ITEM_WORK);
                        break;
                    case R.id.nav_apple:
                        handleCurrentPage("苹果新闻",ITEM_APPLE);
                        break;
                    case R.id.nav_war:
                        handleCurrentPage("军事新闻",ITEM_WAR);
                        break;
                    case R.id.nav_internet:
                        handleCurrentPage("移动互联",ITEM_INTERNET);
                        break;
                    case R.id.nav_travel:
                        handleCurrentPage("旅游资讯",ITEM_TREVAL);
                        break;
                    case R.id.nav_health:
                        handleCurrentPage("健康知识",ITEM_HEALTH);
                        break;
                    case R.id.nav_strange:
                        handleCurrentPage("奇闻异事",ITEM_STRANGE);
                        break;
                    case R.id.nav_looker:
                        handleCurrentPage("美女图片",ITEM_LOOKER);
                        break;
                    case R.id.nav_vr:
                        handleCurrentPage("VR科技",ITEM_VR);
                        break;
                    case R.id.nav_it:
                        handleCurrentPage("IT资讯",ITEM_IT);
                        break;
                    default:
                        break;
                }
                drawerLayout.closeDrawers();
                return true;
            }
        });

        refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                refreshLayout.setRefreshing(true);
                int itemName = parseString((String)actionBar.getTitle());
                requestNew(itemName);
            }
        });

        requestNew(ITEM_SOCIETY);

    }

    /**
     *  判断是否是当前页面,如果不是则 请求处理数据
     */
    private void handleCurrentPage(String text, int item){
        ActionBar actionBar = getSupportActionBar();
        if (!text.equals(actionBar.getTitle().toString())){
            actionBar.setTitle(text);
            requestNew(item);
            refreshLayout.setRefreshing(true);
        }
    }


    /**
     * 请求处理数据
     */
    public void requestNew(int itemName){

        // 根据返回到的 URL 链接进行申请和返回数据
        String address = response(itemName);    // key
        HttpUtil.sendOkHttpRequest(address, new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(MainActivity.this, "新闻加载失败", Toast.LENGTH_SHORT).show();
                    }
                });
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String responseText = response.body().string();
                final NewsList newlist = Utility.parseJsonWithGson(responseText);
                final int code = newlist.code;
                final String msg = newlist.msg;
                if (code == 200){
                    titleList.clear();
                    for (News news:newlist.newsList){
                        Title title = new Title(news.title,news.description,news.picUrl, news.url);
                        titleList.add(title);
                    }

                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            adapter.notifyDataSetChanged();
                            listView.setSelection(0);
                            refreshLayout.setRefreshing(false);
                        };
                    });
                }else{
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this, "数据错误返回",Toast.LENGTH_SHORT).show();
                            refreshLayout.setRefreshing(false);
                        }
                    });
                }



            }
        });


    }

    /**
     * 输入不同的类型选项,返回对应的 URL 链接
     */
    private String response(int itemName){
        String address = "https://api.tianapi.com/social/?key=339a8b166f397f008236e596616a5f54&num=50&rand=1";
        switch(itemName){
            case ITEM_SOCIETY:
                break;
            case ITEM_COUNTY:
                address = address.replaceAll("social","guonei");
                break;
            case ITEM_INTERNATION:
                address = address.replaceAll("social","world");
                break;
            case ITEM_FUN:
                address = address.replaceAll("social","huabian");
                break;
            case ITEM_SPORT:
                address = address.replaceAll("social","tiyu");
                break;
            case ITEM_NBA:
                address = address.replaceAll("social","nba");
                break;
            case ITEM_FOOTBALL:
                address = address.replaceAll("social","football");
                break;
            case ITEM_TECHNOLOGY:
                address = address.replaceAll("social","keji");
                break;
            case ITEM_WORK:
                address = address.replaceAll("social","startup");
                break;
            case ITEM_APPLE:
                address = address.replaceAll("social","apple");
                break;
            case ITEM_WAR:
                address = address.replaceAll("social","military");
                break;
            case ITEM_INTERNET:
                address = address.replaceAll("social","mobile");
                break;
            case ITEM_TREVAL:
                address = address.replaceAll("social","travel");
                break;
            case ITEM_HEALTH:
                address = address.replaceAll("social","health");
                break;
            case ITEM_STRANGE:
                address = address.replaceAll("social","qiwen");
                break;
            case ITEM_LOOKER:
                address = address.replaceAll("social","meinv");
                break;
            case ITEM_VR:
                address = address.replaceAll("social","vr");
                break;
            case ITEM_IT:
                address = address.replaceAll("social","it");
                break;
            default:
        }
        return address;
    }

    /**
     * 通过 actionbar.getTitle() 的参数,返回对应的 ItemName
     */
    private int parseString(String text){
        if (text.equals("社会新闻")){
            return ITEM_SOCIETY;
        }
        if (text.equals("国内新闻")){
            return ITEM_COUNTY;
        }
        if (text.equals("国际新闻")){
            return ITEM_INTERNATION;
        }
        if (text.equals("娱乐新闻")){
            return ITEM_FUN;
        }
        if (text.equals("体育新闻")){
            return ITEM_SPORT;
        }
        if (text.equals("NBA新闻")){
            return ITEM_NBA;
        }
        if (text.equals("足球新闻")){
            return ITEM_FOOTBALL;
        }
        if (text.equals("科技新闻")){
            return ITEM_TECHNOLOGY;
        }
        if (text.equals("创业新闻")){
            return ITEM_WORK;
        }
        if (text.equals("苹果新闻")){
            return ITEM_APPLE;
        }
        if (text.equals("军事新闻")){
            return ITEM_WAR;
        }
        if (text.equals("移动互联")){
            return ITEM_INTERNET;
        }
        if (text.equals("旅游资讯")){
            return ITEM_TREVAL;
        }
        if (text.equals("健康知识")){
            return ITEM_HEALTH;
        }
        if (text.equals("奇闻异事")){
            return ITEM_STRANGE;
        }
        if (text.equals("美女图片")){
            return ITEM_LOOKER;
        }
        if (text.equals("VR科技")){
            return ITEM_VR;
        }
        if (text.equals("IT资讯")){
            return ITEM_IT;
        }
        return ITEM_SOCIETY;
    }

    /**
     * 对侧边栏按钮进行处理,打开侧边栏
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case android.R.id.home:
                drawerLayout.openDrawer(GravityCompat.START);
                break;
            default:
        }
        return true;
    }

    /**
     * 对返回键进行处理,如果侧边栏打开则关闭侧边栏,否则关闭 activity
     */
    @Override
    public void onBackPressed() {
        if(drawerLayout.isDrawerOpen(GravityCompat.START)){
            drawerLayout.closeDrawers();
        }else{
            finish();
        }
    }
}

本文的代码量虽然很大,只是比较繁琐,因为需要根据点击的 ITEM 来对不同的 接口地址提出申请,大部分的函数功能都有进行注释,所以略过了

public void requestNew(int itemName){

        // 根据返回到的 URL 链接进行申请和返回数据
        String address = response(itemName);    // key
        HttpUtil.sendOkHttpRequest(address, new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(MainActivity.this, "新闻加载失败", Toast.LENGTH_SHORT).show();
                    }
                });
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String responseText = response.body().string();
                final NewsList newlist = Utility.parseJsonWithGson(responseText);
                final int code = newlist.code;
                final String msg = newlist.msg;
                if (code == 200){
                    titleList.clear();
                    for (News news:newlist.newsList){
                        Title title = new Title(news.title,news.description,news.picUrl, news.url);
                        titleList.add(title);
                    }

                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            adapter.notifyDataSetChanged();
                            listView.setSelection(0);
                            refreshLayout.setRefreshing(false);
                        };
                    });
                }else{
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this, "数据错误返回",Toast.LENGTH_SHORT).show();
                            refreshLayout.setRefreshing(false);
                        }
                    });
                }



            }
        });


    }
  1. 外部调用时传入 itemName 参数,通过 response() 函数得到所需要请求数据的地址
  2. 通过sendOkHttpRequest() 回调方法,在返回数据成功的 onResponse() 方法中使用 parseJsonWithGson() 方法获取对应的实体类
  3. 将实体类中的数据添加到 Title对应中,将 Title 对象添加到 titleList 中,最后通过 runOnUiThread() 方法,切换到主线程提醒适配器进行数据更新。

至此,主界面的代码逻辑都已经处理好了,还有 ListView 子项布局的点击事件处理:

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            Intent intent = new Intent(MainActivity.this, ContentActivity.class);
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Title title = titleList.get(position);
                intent.putExtra("title",actionBar.getTitle());
                intent.putExtra("uri",title.getUri());
                startActivity(intent);
            }
        });

在点击 ListView 子项布局时,会传入 标题栏文本 和 内容 URL

文件 activity_content.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    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:background="@color/color_White">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >

        <android.support.v7.widget.Toolbar
            android:id="@+id/tool_bar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:titleTextColor="@color/color_White"
            app:theme="@style/ThemeOverlay.AppCompat.Light"
            app:layout_scrollFlags="enterAlways|snap|scroll"/>

    </android.support.design.widget.AppBarLayout>


    <WebView
        android:id="@+id/web_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>



</android.support.design.widget.CoordinatorLayout>

如果了解了 activity_main.xml 的布局,这个布局也就没什么难度了,主要是新增了 WebView 控件,用来显示传入的 URL

文件 ContentActivity.class:

public class ContentActivity extends AppCompatActivity {
    private WebView webView;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_content);

        Toolbar toolbar = (Toolbar)findViewById(R.id.tool_bar);
        setSupportActionBar(toolbar);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null){
            actionBar.setDisplayHomeAsUpEnabled(true);
            actionBar.setHomeAsUpIndicator(R.drawable.ic_back);
        }

        webView = (WebView)findViewById(R.id.web_view);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebViewClient(new WebViewClient());

        String uri = getIntent().getStringExtra("uri");
        String title = getIntent().getStringExtra("title");
        actionBar.setDisplayShowTitleEnabled(true);
        actionBar.setTitle(title);
        webView.loadUrl(uri);

    }

    /**
     * 点击返回键做了处理
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case android.R.id.home:
                finish();
                break;
            default:
        }
        return true;
    }
}

显示传入的 URL网址


最后


到这里就结束了 ? 如果你认为都结束了,那你可以就需要面对打开应用之后马上闪退的情况了…….权限

我们还没有对权限进行申请,在 AndroidManifest 文件中添加声明:

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

不过个人还是建议把 权限的考虑放在最先的优先级,毕竟养成这个习惯,就可以专注于代码的 bug…………………..


运行界面


这里写图片描述
(主界面)

这里写图片描述
(侧边栏)

这里写图片描述
(内容界面)


运行 GIF


这里写图片描述
(由于大小限制,所以就只能传这么大了)


完整代码下载地址


https://github.com/lentitude/NewsMD

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页