跟代码相关的工作,大多唯手熟尔,所以这里花了点时间做了款简易版的新闻 APP,虽然都是些基础的内容,不过还是可以加深自己对部分代码的理解。至少,可以加深自己的记忆
步骤
依赖库
过程中需要用到一些开源依赖库文件,先在 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 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>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary
- "colorPrimaryDark"
>@color/colorPrimary
- "colorAccent">@color/colorAccent
style>
resources>
修改通知栏颜色和标题栏颜色一样,是处于视觉统一的原因,也可以不修改(非必须)
主要采用的是 Material Design 的设计,整体布局采用的是滑动菜单,主界面内容为 ToolBar 和 ListView(这里为了方便,就直接使用),侧边栏内容为 NavigationView
主界面:
因为要用 ToolBar 替代 ActionBar, 我们先修改 values 下面的 styles 文件,修改主题为:
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
在layout 下创建 nav_header 文件
<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 文件:
version="1.0" encoding="utf-8"?>
这里创建了若干个 ITEM 子项,只有 title,没有 icon,大家可以自行放置
主界面 activity_main.xml:
"1.0" encoding="utf-8"?>
.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"
>
.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
.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"
/>
.support.design.widget.AppBarLayout>
.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">
"@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@color/color_Background"
android:dividerHeight="1dp"
/>
.support.v4.widget.SwipeRefreshLayout>
.support.design.widget.CoordinatorLayout>
.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"
/>
.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 的子项布局:
<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 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 titleList = new ArrayList();
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);
}
});
}
}
});
}
至此,主界面的代码逻辑都已经处理好了,还有 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:
"1.0" encoding="utf-8"?>
.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">
.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
.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"/>
.support.design.widget.AppBarLayout>
"@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
.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