二、代码结构:

三、布局文件
1.activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.qingshan.listview.MyListView android:id="@+id/list_view" android:layout_width="wrap_content" android:layout_height="wrap_content" tools:ignore="MissingConstraints" /> </androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:id="@+id/head" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal" android:padding="5dp"> <ProgressBar android:id="@+id/headprogress" style="?android:attr/progressBarStyleInverse" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/headtxt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="正在刷新......." /> </LinearLayout> </LinearLayout>3.footer.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:gravity="center_horizontal" android:layout_gravity="center_vertical" android:padding="10dp"> <LinearLayout android:id="@+id/foot_load" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:gravity="center" android:orientation="horizontal"> <ProgressBar style="?android:attr/progressBarStyleInverse" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="加载中,请稍候..." /> </LinearLayout> </LinearLayout>4.listview_item.xml
<?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="wrap_content" android:padding="10dp"> <ImageView android:id="@+id/logo" android:layout_width="80dp" android:layout_height="90dp" android:layout_centerVertical="true" android:padding="5dp" /> <TextView android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_toRightOf="@id/logo" android:gravity="center_vertical" android:text="" android:textSize="14sp" android:textStyle="bold" /> <TextView android:id="@+id/summary" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/title" android:layout_marginTop="8dp" android:layout_toRightOf="@id/logo" android:text="" android:textSize="12sp" /> </RelativeLayout>四、程序编写
1.先做好数据接口,如:http://qingshanboke.com/home/AndoridArticleList?lastId=1753&pagesize=5
通过lastId+分页大小,获取一页数据。
2.创建实体类:MyArticle.java
package com.qingshan.listview; public class MyArticle { public int Id; public String Title; public String Logo; public String Summary; public MyArticle(int id, String title) { Id = id; Title = title; } public MyArticle(int id, String title, String logo) { Id = id; Title = title; Logo = logo; } public MyArticle(int id, String title, String logo, String summary) { Id = id; Title = title; Logo = logo; Summary = summary; } public String getTitle() { return Title.length() > 18 ? Title.substring(0, 18) + "..." : Title; } public String getSummary() { return Summary.length() >50 ? Summary.substring(0, 50) + "..." : Summary; } public String getLogo() { return Logo == null ? "" : (Logo.startsWith("/Upload") ? "http://qingshanboke.com" + Logo : Logo); } }3.创建读取数据类:DataProvider.java
package com.qingshan.listview; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class DataProvider { public static ArrayList<MyArticle> GetList(int lastid) { ArrayList<MyArticle> list = new ArrayList<MyArticle>(); String url = "http://qingshanboke.com/home/AndoridArticleList"; Map<String, String> dic = new HashMap<String, String>(); dic.put("lastId", String.valueOf(lastid)); dic.put("pagesize", "10"); String json = Post(url, dic, null); try { JSONArray array = new JSONArray(json); for (int i = 0; i < array.length(); i++) { JSONObject object = array.getJSONObject(i); if (object != null) { int id =Integer.parseInt( object.getString("id")); String title = String.valueOf(object.getString("title")); String logo = String.valueOf(object.getString("logo")); String summary = String.valueOf(object.getString("summary")).replace("\n","").trim(); list.add(new MyArticle(id, title,logo,summary)); } } } catch (JSONException e) { e.printStackTrace(); } return list; } public static String Post(String posturl, Map<String, String> textMap, Map<String, String> fileMap) { String res = ""; HttpURLConnection conn = null; String BOUNDARY = "---------------------------" + System.currentTimeMillis(); //boundary就是request头和上传文件内容的分隔符 try { URL url = new URL(posturl); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setReadTimeout(30000); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); conn.setRequestMethod("POST"); conn.setRequestProperty("Connection", "Keep-Alive"); conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)"); conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); OutputStream out = new DataOutputStream(conn.getOutputStream()); // text if (textMap != null) { StringBuffer buffer = new StringBuffer(); Iterator iter = textMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); String inputName = (String) entry.getKey(); String inputValue = (String) entry.getValue(); if (inputValue == null) { continue; } buffer.append("\r\n").append("--").append(BOUNDARY).append("\r\n"); buffer.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n"); buffer.append(inputValue); } out.write(buffer.toString().getBytes()); } // file if (fileMap != null) { Iterator iter = fileMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); String inputName = (String) entry.getKey(); String inputValue = (String) entry.getValue(); if (inputValue == null) { continue; } File file = new File(inputValue); String filename = file.getName(); String contentType = ""; if (filename.endsWith(".jpg")) { contentType = "image/jpg"; } else if (filename.endsWith(".png")) { contentType = "image/png"; } else if (contentType == null || contentType.equals("")) { contentType = "application/octet-stream"; } StringBuffer buffer = new StringBuffer(); buffer.append("\r\n").append("--").append(BOUNDARY).append("\r\n"); buffer.append("Content-Disposition: form-data; name=\"" + inputName + "\"; filename=\"" + filename + "\"\r\n"); buffer.append("Content-Type:" + contentType + "\r\n\r\n"); out.write(buffer.toString().getBytes()); DataInputStream in = new DataInputStream(new FileInputStream(file)); int bytes = 0; byte[] bufferOut = new byte[1024]; while ((bytes = in.read(bufferOut)) != -1) { out.write(bufferOut, 0, bytes); } in.close(); } } byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes(); out.write(endData); out.flush(); out.close(); // 读取返回数据 StringBuffer buffer = new StringBuffer(); BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line = null; while ((line = reader.readLine()) != null) { buffer.append(line).append("\n"); } res = buffer.toString(); reader.close(); reader = null; } catch (Exception e) { System.out.println("发送POST请求出错。" + posturl); e.printStackTrace(); } finally { if (conn != null) { conn.disconnect(); conn = null; } } return res; } }4.创建自定义ListView控件:MyListView.java
package com.qingshan.listview; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.AbsListView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; public class MyListView extends ListView implements AbsListView.OnScrollListener { private View footer; private View header; private int footerHeight; private int headerHeight; private int total; private int last; private int first; private int visibleItemCount; private LoadListener loadListener; boolean isLoading; private TextView headerText; private TextView headerTime; private ProgressBar progressBar; public MyListView(Context context) { super(context); Init(context); } public MyListView(Context context, AttributeSet attrs) { super(context, attrs); Init(context); } public MyListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); Init(context); } private void Init(Context context) { //头布局 header = LinearLayout.inflate(context, R.layout.header, null); headerText = header.findViewById(R.id.headtxt); progressBar = header.findViewById(R.id.headprogress); header.measure(0, 0); headerHeight = header.getMeasuredHeight(); header.setPadding(0, -headerHeight, 0, 0);//隐藏 //尾布局 footer = LinearLayout.inflate(context, R.layout.footer, null); footer.measure(0, 0); footerHeight = footer.getMeasuredHeight(); footer.setPadding(0, -footerHeight, 0, 0);//隐藏 this.addFooterView(footer); this.addHeaderView(header); this.setOnScrollListener(this);//设置拉动监听 } @Override public boolean onTouchEvent(MotionEvent ev) { // switch (ev.getAction()) { // case MotionEvent.ACTION_DOWN: // postion = (int) ev.getY(); // break; // // case MotionEvent.ACTION_MOVE: // int moveY = (int) ev.getY(); // int paddingY = headerHeight + (moveY - postion) / 2; // if (paddingY < 0) { // headerText.setText("下拉刷新........"); // progressBar.setVisibility(View.GONE); // } // if (paddingY > 0) { // headerText.setText("松开刷新........"); // progressBar.setVisibility(View.GONE); // } // header.setPadding(0, paddingY, 0, 0); // break; // } return super.onTouchEvent(ev); } public void onScrollStateChanged(AbsListView view, int scrollState) { //触发上拉事件 if (total == last && scrollState == SCROLL_STATE_IDLE) { if (!isLoading) { isLoading = true; footer.setPadding(0, 0, 0, 0); loadListener.onLoadMore();//加载数据 } } //Log.i("TGA", "firstVisible----" + first); //Log.i("TGA", "状态?" + (first == 0)); //触发下拉事件 if (first == 0 && scrollState == SCROLL_STATE_IDLE) { header.setPadding(0, 0, 0, 0); headerText.setText("努力加载中..."); progressBar.setVisibility(View.VISIBLE); loadListener.onRefush(); } } //接口回调 public interface LoadListener { void onLoadMore(); void onRefush(); } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { this.first = firstVisibleItem; this.last = firstVisibleItem + visibleItemCount; this.total = totalItemCount; this.visibleItemCount = visibleItemCount; //Log.i("TGA", this.first + " " + this.last + " " + this.total); } //加载完成 public void loadComplete() { isLoading = false; footer.setPadding(0, -footerHeight, 0, 0); header.setPadding(0, -headerHeight, 0, 0); setSelection(last - visibleItemCount+1);//设置listview的当前位置,如果不设置每次加载完后都会返回到list的第一项 } public void setInteface(LoadListener loadListener) { this.loadListener = loadListener; } }5.创建ListView对应的数据适配器:MyAdapter.java
package com.qingshan.listview; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.os.AsyncTask; import android.util.Log; import android.util.LruCache; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import androidx.annotation.NonNull; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.List; public class MyAdapter extends ArrayAdapter { private LruCache<String, BitmapDrawable> cache;//用于缓存图片 private ListView listview; private int cnt = 0; public MyAdapter(@NonNull Context context, int resource) { super(context, resource); } public MyAdapter(Context context, int resource, List<MyArticle> objects) { super(context, resource, objects); //在实例化LruCache的时候,我们需要传入一个参数,表明我们可以使用的最大缓存, //这个缓存参数我们传入可用缓存的1/8, //同时我们需要重写sizeOf方法,查看源码我们可以知道,如果不重写sizeOf方法,它默认返回的是图片的数量, //但是我们实际上是需要计算图片大小来判断当前已经使用的缓存是否已经超出界限,所以我们这里重写sizeOf方法,返回每张图片的大小。 int maxCache = (int) Runtime.getRuntime().maxMemory(); int cacheSize = maxCache / 8; cache = new LruCache<String, BitmapDrawable>(cacheSize) { @Override protected int sizeOf(String key, BitmapDrawable value) { return value.getBitmap().getByteCount(); } }; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (listview == null) { listview = (ListView) parent; } //获取渲染实体信息 MyArticle article = (MyArticle) getItem(position); ViewHolder viewHolder; if (convertView == null) { convertView = LayoutInflater.from(getContext()).inflate(R.layout.listview_item, null);//查找视图 viewHolder = new ViewHolder(); viewHolder.title = convertView.findViewById(R.id.title); viewHolder.logo = convertView.findViewById(R.id.logo); viewHolder.summary = convertView.findViewById(R.id.summary); convertView.setTag(viewHolder);//将视图保存到ViewHolder } else { viewHolder = (ViewHolder) convertView.getTag(); } //处理文本 viewHolder.title.setText(article.getTitle()); viewHolder.summary.setText(article.getSummary()); //处理图片:如果本地已有缓存,就从本地读取,否则从网络请求数据 String imageUrl = article.getLogo(); viewHolder.logo.setImageResource(R.drawable.ic_launcher_background);//预设一个图片,比如loading viewHolder.logo.setTag(imageUrl);//通过tag来防止图片错位,下面下载图片成功后,会用到 findViewWithTag // //AsyncTask子类的实例必须在UI线程中创建 // ImageTask task = new ImageTask(); // // //手动调用execute(Params... params) 从而执行异步线程任务 // // 注: // //a. 必须在UI线程中调用 // //b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常 // //c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute() // //d. 不能手动调用上述方法 // task.execute(imageUrl); // Log.i("加载图片", imageUrl); if (imageUrl.length() > 0) { if (cache.get(imageUrl) != null) { BitmapDrawable drawable = cache.get(imageUrl); viewHolder.logo.setImageDrawable(drawable); Log.i("缓存设置图片", imageUrl); } else { //AsyncTask子类的实例必须在UI线程中创建 ImageTask task = new ImageTask(); //手动调用execute(Params... params) 从而执行异步线程任务 // 注: //a. 必须在UI线程中调用 //b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常 //c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute() //d. 不能手动调用上述方法 task.execute(imageUrl); } } return convertView; } //列表渲染优化,缓存列表控件 class ViewHolder { TextView title; ImageView logo; TextView summary; } // public abstract class AsyncTask<Params, Progress, Result>{}类中参数为3种泛型类型 // 整体作用:控制AsyncTask子类执行线程任务时各个阶段的返回类型 // 具体说明: // a. Params:开始异步任务执行时传入的参数类型,对应excute()中传递的参数 // b. Progress:异步任务执行过程中,返回下载进度值的类型 // c. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致 // 注: // a. 使用时并不是所有类型都被使用 // b. 若无被使用,可用java.lang.Void类型代替 // c. 若有不同业务,需额外再写1个AsyncTask的子类 class ImageTask extends AsyncTask<String, Void, BitmapDrawable> { String imageUrl; // 方法1:onPreExecute() // 作用:执行线程任务前的操作 // 注:根据需求复写 // @Override // protected void onPreExecute() { // super.onPreExecute(); // } // 方法2:doInBackground() // 作用:接收输入参数、执行任务中的耗时操作、返回线程任务执行的结果 // 注:必须复写,从而自定义线程任务 @Override protected BitmapDrawable doInBackground(String... params) { imageUrl = params[0]; if (cache.get(imageUrl) == null) { Bitmap bitmap = downloadImage(); BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), bitmap); cache.put(imageUrl, drawable); return drawable; } else { return cache.get(imageUrl); } } // 方法3:onProgressUpdate() // 作用:在主线程 显示线程任务执行的进度 // 注:根据需求复写 // @Override // protected void onProgressUpdate(Integer... progresses) { // // // } // 方法4:onPostExecute() // 作用:接收线程任务执行结果、将执行结果显示到UI组件 // 注:必须复写,从而自定义UI操作 @Override protected void onPostExecute(BitmapDrawable result) { // 通过Tag找到我们需要的ImageView,如果该ImageView所在的item已被移出页面,就会直接返回null ImageView imageView = listview.findViewWithTag(imageUrl); if (imageView != null && result != null) { imageView.setImageDrawable(result); } } // 方法5:onCancelled() // 作用:将异步任务设置为:取消状态 //@Override //protected void onCancelled() { super.onCancelled(); } //根据url从网络上下载图片 private Bitmap downloadImage() { HttpURLConnection con = null; Bitmap bitmap = null; try { URL url = new URL(imageUrl); con = (HttpURLConnection) url.openConnection(); con.setConnectTimeout(5 * 1000); con.setReadTimeout(10 * 1000); bitmap = BitmapFactory.decodeStream(con.getInputStream()); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (con != null) { con.disconnect(); } } return bitmap; } } }6.入口Activity调用ListView:MainActivity.java
package com.qingshan.listview; import android.Manifest; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.provider.Settings; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity implements MyListView.LoadListener { private MyListView listViewScroll; private List<MyArticle> list = new ArrayList<>(); private MyAdapter adapter; private int lastId = 0; private final int permissionCode = 100;//权限请求码 String[] permissions = new String[]{ Manifest.permission.ACCESS_NETWORK_STATE, Manifest.permission.ACCESS_WIFI_STATE, Manifest.permission.INTERNET }; AlertDialog alertDialog; private final int PULLUP = 1;//上拉 private final int DROPDOWN = 2;//下拉 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //adapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_expandable_list_item_1, list); adapter = new MyAdapter(MainActivity.this, R.layout.listview_item, list); listViewScroll = findViewById(R.id.list_view); listViewScroll.setInteface(MainActivity.this); listViewScroll.setAdapter(adapter); //6.0才用动态权限 if (Build.VERSION.SDK_INT >= 23) { checkPermission(); } } public Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case DROPDOWN: { list.clear(); ArrayList<MyArticle> data = (ArrayList<MyArticle>) msg.obj; for (int i = 0; i < data.size(); i++) { MyArticle article = data.get(i); if (!list.contains(article)) { list.add(article); } if (i == data.size() - 1) { lastId = data.get(i).Id; } } adapter.notifyDataSetChanged(); listViewScroll.loadComplete(); Toast.makeText(MainActivity.this, "已加载" + list.size() + "条记录", Toast.LENGTH_SHORT).show(); } case PULLUP: { ArrayList<MyArticle> data = (ArrayList<MyArticle>) msg.obj; for (int i = 0; i < data.size(); i++) { MyArticle article = data.get(i); if (!list.contains(article)) { list.add(article); } if (i == data.size() - 1) { lastId = data.get(i).Id; } } adapter.notifyDataSetChanged(); listViewScroll.loadComplete(); Toast.makeText(MainActivity.this, "已加载" + list.size() + "条记录", Toast.LENGTH_SHORT).show(); } break; } } }; public void Load(final int lastId) { // Android 4.0 之后不能在主线程中请求HTTP请求 new Thread(new Runnable() { @Override public void run() { //通过Message+Handler方式,将网络获取数据传递给主线程 Message message = new Message(); ArrayList<MyArticle> data = DataProvider.GetList(lastId); message.obj = data; message.what = lastId == 0 ? DROPDOWN : PULLUP; handler.sendMessage(message); } }).start(); } //检查权限 private void checkPermission() { List<String> permissionList = new ArrayList<>(); for (int i = 0; i < permissions.length; i++) { if (ContextCompat.checkSelfPermission(this, permissions[i]) != PackageManager.PERMISSION_GRANTED) { permissionList.add(permissions[i]); } } if (permissionList.size() <= 0) { //说明权限都已经通过,可以做你想做的事情去 Load(0); } else { //存在未允许的权限 ActivityCompat.requestPermissions(this, permissions, permissionCode); } } //授权后回调函数 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); boolean haspermission = false; if (permissionCode == requestCode) { for (int i = 0; i < grantResults.length; i++) { if (grantResults[i] == -1) { haspermission = true; } } if (haspermission) { //跳转到系统设置权限页面,或者直接关闭页面,不让他继续访问 permissionDialog(); } else { //全部权限通过,可以进行下一步操作 Load(0); } } } //打开手动设置应用权限 private void permissionDialog() { if (alertDialog == null) { alertDialog = new AlertDialog.Builder(this) .setTitle("提示信息") .setMessage("当前应用缺少必要权限,该功能暂时无法使用。如若需要,请单击【确定】按钮前往设置中心进行权限授权。") .setPositiveButton("设置", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { cancelPermissionDialog(); Uri packageURI = Uri.parse("package:" + getPackageName()); Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI); startActivity(intent); } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { cancelPermissionDialog(); } }) .create(); } alertDialog.show(); } //用户取消授权 private void cancelPermissionDialog() { alertDialog.cancel(); } //上提,加载更多 @Override public void onLoadMore() { Load(lastId); //模拟耗时操作 // new Handler().postDelayed(new Runnable() { // @Override // public void run() { // Load(lastId); // } // }, 1); } //下拉刷新 @Override public void onRefush() { Load(0); //模拟耗时操作 //new Handler().postDelayed(new Runnable() { // @Override // public void run() { // Load(0); // } // }, 1); } }7.AndroidManifest.xml 配置相应权限
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.qingshan.listview"> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.INTERNET"/> <application android:networkSecurityConfig="@xml/network_security_config" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>8.配置允许Http的站点。src\main\res\xml\network_security_config.xml
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true" /> <domain-config cleartextTrafficPermitted="true" > <domain includeSubdomains="true">127.0.0.1</domain> <domain includeSubdomains="true">192.168.100.192</domain> <domain includeSubdomains="true">localhost</domain> <domain includeSubdomains="true">qingshanboke.com</domain> </domain-config> </network-security-config>注意事项:
这个ListView下拉刷新,上拉加载更多,图文并排示例,用到的知识点挺多的:
1.Android 4.0 之后不能在主线程中请求HTTP请求,需使用Message+Handler方式,将网络获取数据传递给主线程。
2.AsyncTask异步下载图片(详见MyAdapter.java类代码注释)。
3.数据适配器Adapter的getView方法优化。(详见MyAdapter.java类代码注释)。