Android 实践:做一款新闻 APP
2017-05-06 11:17
176 查看
跟代码相关的工作,大多唯手熟尔,所以这里花了点时间做了款简易版的新闻 APP,虽然都是些基础的内容,不过还是可以加深自己对部分代码的理解。至少,可以加深自己的记忆
步骤
依赖库
网络请求
网络解析
界面布局
最后
运行界面
运行GIF
完整代码下载地址(github)
依赖库
过程中需要用到一些开源依赖库文件,先在 build.grade 中声明:
网络请求
在包下创建一个文件夹 util 用来存放工具类,创建文件 HttpUtil.class 用来请求数据:
这里用到的是 okhttp3.Callback 的回调接口,结果会返回到 callback 的回调函数中,后面会进行处理
网络解析
我们先从数据解析开始,毕竟这才是这个小项目的重点。这次项目使用的数据来源是天行数据(http://www.tianapi.com/ )的新闻资讯 API ,先看 API 的说明:
![](https://img-blog.csdn.net/20170506093055476?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWl3ZWkxMg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到返回数据为 JSON, 默认返回 10 条参数。请求地址为:
![](https://img-blog.csdn.net/20170506094612844?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWl3ZWkxMg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
其中, APIKEY 需要用个人的 API KEY 代替,可以在个人中心中看到,其他的请求地址也是大同小异
![](https://img-blog.csdn.net/20170506<br/>4000<br/>094853735?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWl3ZWkxMg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
JSON 返回示例:
![](https://img-blog.csdn.net/20170506093317804?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWl3ZWkxMg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
还有错误返回码,用来判断返回数据的异常情况:
![](https://img-blog.csdn.net/20170506093436040?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWl3ZWkxMg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
根据 gson 的返回示例,我们可以写出对应的实体类文件,通过 gson 将返回数据转化为对应的类型。先创建一个 gson 文件夹存放实体类文件。
在 gson 文件夹下创建 New.class 文件:
创建 NewsList.class 文件:
至此,我们就已经创建好了与返回数据对应的实体类。
在 util 文件夹下创建文件 Utility.class 文件:
将请求得到的数据解析为 NewList 实体类对象。现在网络请求和解析都准备好了,就开始界面文件了
界面布局
修改 values 目录下的 styles.xml 文件:
修改通知栏颜色和标题栏颜色一样,是处于视觉统一的原因,也可以不修改(非必须)
主要采用的是 Material Design 的设计,整体布局采用的是滑动菜单,主界面内容为 ToolBar 和 ListView(这里为了方便,就直接使用),侧边栏内容为 NavigationView
主界面:
因为要用 ToolBar 替代 ActionBar, 我们先修改 values 下面的 styles 文件,修改主题为:
在layout 下创建 nav_header 文件
这里在头部文件中放置了一个CircleImageView,两个 TextView,没有什么理解难度
在 res 目录下创建 menu 文件夹,新建 nav_menu.xml 文件:
这里创建了若干个 ITEM 子项,只有 title,没有 icon,大家可以自行放置
主界面 activity_main.xml:
因为是一步到位,所以……大家最好之前用过使用过相同的布局设计(比如:第一行代码)
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 的子项布局:
子项布局内包含 3 个控件,ImageView 显示返回的图片,TextView 显示返回的标题和出处
创建一个 Title.class类:
这里之所以除了 标题,出处,图片显示在 ListViw 中,uri 传入另一个布局,显示内容文件
接下来就是 TitleAdapter.class
这里还是一样的老套路,通过convertView 来缓存布局,通过类 ViewHolder 缓存控件实例,这样做,可以节省 50% 的效率,所以还是按照老套路走吧。
接下来就是 Activity 文件 MainActivity.class:
本文的代码量虽然很大,只是比较繁琐,因为需要根据点击的 ITEM 来对不同的 接口地址提出申请,大部分的函数功能都有进行注释,所以略过了
外部调用时传入 itemName 参数,通过 response() 函数得到所需要请求数据的地址
通过sendOkHttpRequest() 回调方法,在返回数据成功的 onResponse() 方法中使用 parseJsonWithGson() 方法获取对应的实体类
将实体类中的数据添加到 Title对应中,将 Title 对象添加到 titleList 中,最后通过 runOnUiThread() 方法,切换到主线程提醒适配器进行数据更新。
至此,主界面的代码逻辑都已经处理好了,还有 ListView 子项布局的点击事件处理:
在点击 ListView 子项布局时,会传入 标题栏文本 和 内容 URL
文件 activity_content.xml:
如果了解了 activity_main.xml 的布局,这个布局也就没什么难度了,主要是新增了 WebView 控件,用来显示传入的 URL
文件 ContentActivity.class:
显示传入的 URL网址
最后
到这里就结束了 ? 如果你认为都结束了,那你可以就需要面对打开应用之后马上闪退的情况了…….权限
我们还没有对权限进行申请,在 AndroidManifest 文件中添加声明:
不过个人还是建议把 权限的考虑放在最先的优先级,毕竟养成这个习惯,就可以专注于代码的 bug…………………..
运行界面
![](https://img-blog.csdn.net/20170506105823627?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWl3ZWkxMg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
(主界面)
![](https://img-blog.csdn.net/20170506105847584?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWl3ZWkxMg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
(侧边栏)
![](https://img-blog.csdn.net/20170506105908131?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWl3ZWkxMg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
(内容界面)
运行 GIF
![](https://img-blog.csdn.net/20170506111744033?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWl3ZWkxMg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
(由于大小限制,所以就只能传这么大了)
完整代码下载地址
https://github.com/lentitude/NewsMD
步骤
依赖库
网络请求
网络解析
界面布局
最后
运行界面
运行GIF
完整代码下载地址(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 的说明:
可以看到返回数据为 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" a 15d82 ndroid: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); } }); } } }); }
外部调用时传入 itemName 参数,通过 response() 函数得到所需要请求数据的地址
通过sendOkHttpRequest() 回调方法,在返回数据成功的 onResponse() 方法中使用 parseJsonWithGson() 方法获取对应的实体类
将实体类中的数据添加到 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
相关文章推荐
- 如何优雅的使用Retrofit、Rxjava、Butterknife、Material开发一款MVP模式的新闻+天气预报+妹子的Android app
- Android 实践:做一款可用的天气 APP
- 练习项目 一款新闻app的开发 (四):通过RecyclerView来展示新闻列表
- 携程Android App插件化和动态加载实践
- Android新手如何学习开发一款app?
- Android Material Design 风格的新闻App
- Android开发实践(五)App的登陆界面
- 携程Android App插件化和动态加载实践
- 如何用一周时间开发一款Android APP并在Google Play上线
- android新闻App源码、仿微信源码、地图音乐源码等
- BlueStacks 一款让Android App无处不在的神器
- App设计实践——Android篇
- Android实践 -- App的静默安装和卸载
- 从一款被篡改的软件谈 Android App 的安全之路
- Android 新闻App的开发思路
- Android App 性能优化实践
- 从零开始打造一个新闻订阅APP之Android篇(一、实现仿微信主界面效果)
- 萌土1.0,一款社交新闻APP
- 携程Android App插件化和动态加载实践
- 用HTML5来开发一款android本地化App游戏-宝石碰碰