今天在写一个程序。从接口获取数据解析后显示到自己的布局中:
先不说出现的问题先,总结一下这样一个程序的大体逻辑思路---->
大致步骤:
1:编写主布局(添加listview)
2:自定义listview的样式
:3:自定义适配器:BaseAdapter //这个过程可先伪造一些数据源进行调试,检查自己的程序
4:创建异步任务(AsyncTask)
o在doInBackground(该方法子线程执行)方法中执行网络访问的耗时操作(可使用httpurlconnection、okhttp)
o将得到的数据进行解析:可在doInBackground()也可在onPostExecute()方法中解析,并将解析的数据作为付给布局中数据源
5:使用适配器和数据源
问题:加载第一页listview时,设置在底部visiable状态为gone的按钮;占用了listview的位置:
效果图:
(存在问题:第一页加载时有空白,留意底部有一个空白占用着位置,但滑动之后空白消失)
解决后图片:
最后附上在底部弹出按钮的效果图:
上程序代码;
1:主布局的xml文件
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.administrator.qiubai10.MainActivity"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="加载更多..." android:background="@color/colorAccent" android:id="@+id/btn_loadmore" android:layout_alignParentBottom="true" android:visibility="gone"/> <ListView android:layout_above="@id/btn_loadmore" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/listview"></ListView> </RelativeLayout>
2:自定义listview的样式:(只有一个textview)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textview_content"/>
</LinearLayout>
<--开发中使用相对布局的较多,因为在实现复杂界面时需要多层的嵌套,过度的渲染,而且在作修改时对其他的控件影响较大,但相对布局的控件等是较为独立的->
3:自定义适配器:
package com.example.administrator.qiubai10;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.List;
/**
* Created by Administrator on 2016/9/1.
*/
public class Myadapter extends BaseAdapter {
List<QiuBai> list ;
Context context ;
LayoutInflater inflater ;
public Myadapter(List<QiuBai> list, Context context) {
this.list = list;
this.context = context;
this.inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null ;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = inflater.inflate(R.layout.listview_item, parent, false);
viewHolder.content = (TextView) convertView.findViewById(R.id.textview_content);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder)convertView.getTag();
}
if(position%2==0){ //这里是为了相邻两个textview不同的背景颜色
convertView.setBackgroundColor(context.getResources().getColor(R.color.colorHui));
}else{
convertView.setBackgroundColor(context.getResources().getColor(R.color.colorWhite));
}
QiuBai qiuBai = list.get(position);
viewHolder.content.setText(qiuBai.getContent());
return convertView;
}
class ViewHolder{
TextView content ;
}
}
上面出现的 QiuBai 的类是我用来保存网络下载的数据对应的属性。后面我的数据源就是我的 QiuBai 对象的集合(QiuBai的代码在最后贴上)
4:主类(异步任务类为其内部类,方便访问主类的属性)
该类完成网络请求数据,对数据解析,初始化数据源,并且设置listview的适配器。
package com.example.administrator.qiubai10;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.AbsListView;
import android.widget.Button;
import android.widget.ListView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
List<QiuBai> list = new ArrayList<QiuBai>();
private ListView listview;
private Button btn_load;
static int page =1;
String path = "http://m2.qiushibaike.com/article/list/suggest?page=%s";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listview = ((ListView) findViewById(R.id.listview));
btn_load = ((Button) findViewById(R.id.btn_loadmore));
String url = String.format(path,String.valueOf(page));
new MyTask().execute(url);
btn_load.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if(v.getId()==R.id.btn_loadmore){
String url = String.format(path,String.valueOf(++page));
Log.d("bigname_log", "onClick: "+url);
new MyTask().execute(url);
}
}
//异步任务类:请求网络数据,解析后设置到list中
class MyTask extends AsyncTask<String, Void, List<QiuBai>> {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected List<QiuBai> doInBackground(String... params) {
byte[] bytes = HttpUtils.getDatas(params[0]);
String data = new String(bytes, 0, bytes.length);
List<QiuBai> qiuBais = parseJson(data);
list.addAll(qiuBais);
return list;
}
@Override
protected void onPostExecute(List<QiuBai> qiuBais) {
Log.d("bigname_log", "onPostExecute: 加载适配器");//**************************************************************
listview.setAdapter(new Myadapter(list, MainActivity.this));
listview.setOnScrollListener(new AbsListView.OnScrollListener() {
/*
* 1 abslistview view:
* listview对象
* 2 int scrollstate:
* listview的状态,有三种:
* 0:停止
* 1:滑动
* 2:抛掷
* */
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
Log.d("bigname_log", "onScrollStateChanged: "+scrollState);
}
/*
* abslistview view: listview对象
* int firstvisiableitem: 第一个可见item的位置
* int totalItemcount: 总的item个数(长度)
* int visibleitemcount: 可见item的个数
* **/
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Log.d("bigname_log", "onScroll: 测量"); //*****************************************
if ((firstVisibleItem + visibleItemCount) == totalItemCount) {
Log.d("bigname_log", "onScroll: 等于总"); //********************************************
btn_load.setVisibility(View.VISIBLE);
//此时的button悬浮在listview的上方,(覆盖)。应该是因为布局更新的问题。可调用listview的requestlayout()来重新加载
}else{
Log.d("bigname_log", "onScroll: 不等于总");//****************************************
btn_load.setVisibility(View.GONE);
listview.requestLayout();
}
<span style="white-space:pre"> </span>//????????????????????????????????????
}
});
// Log.d("bigname_log", "onPostExecute: ---加载适配器-----");
}
}
//写成静态方法,方便其他的类使用
private static List<QiuBai> parseJson(String data) {
// 将每次访问的一页数据保存在page_list中,返回在添加到总的list中,这样在点击加载更多的时候之前的数据也还在
List<QiuBai> page_list = new ArrayList<QiuBai>();
try {
JSONObject jo = new JSONObject(data);
JSONArray items = jo.getJSONArray("items");
for (int i = 0; i < items.length(); i++) {
JSONObject datas = items.getJSONObject(i);
String id = datas.getString("id");
String content = datas.getString("content");
page_list.add(new QiuBai(content, id));
}
} catch (JSONException e) {
e.printStackTrace();
}
return page_list;
}
}
如标题所说:listview滚动到达底部后,按钮弹出,但还是占了listview位置的问题。
请留意//******************************对应代码的位置。
setAdapter()在前,onScroll()在后,
这样的代码是不会出现这个问题的,但我之前是:onScroll()在前,setAdapter()在后。这样就出现了第一个页面button的位置占了listview的位置。活动一下才消失。
原因应该是listview绘制的问题。在第一次加载的时候不论你是否滑动了,他都会去调用一次onScroll()方法,如果此时的setAdapter()还没把数据设置好的话他就会预留这个位置。虽然我在activity_main.xml中已经把button设为gone了。
有一点我很不理解:为什么跟我onScroll()写的位置有关,不是我写哪里都可以,滑动一次被监听到才执行的吗?虽然结果确实有关,但却不知道为什么?
--》先说明一下什么时候才回去第一次调用onScroll方法:
度娘告诉我:
可是,我们会发现,当运行程序时,listview明明没有滚动,那为什么系统会调用onScroll方法呢?(补充:此时onScrollStateChanged并不会调用)
我们先看setOnScrollListener源码:
public void setOnScrollListener(OnScrollListener l) { //当你在设置监听的时候里面一般传一个匿名内部类,此时l就等于你那个匿名内部类,也就是说l已经不为空
mOnScrollListener = l;
invokeOnItemScrollListener();
}
setOnScrollListener里面调用了invokeOnItemScrollListener()方法,接着看该方法源码:
void invokeOnItemScrollListener() {
if (mFastScroller != null) {
mFastScroller.onScroll(mFirstPosition, getChildCount(), mItemCount);
}
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);//这里调用onScroll,一切真相大白了。
}
onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
}
所以:!setScrollListener()的设置真的跟你设置的位置有关系的,现在终于弄懂了一点,也就是说,你在哪写的setOnscrollListener(),onScroll就会在哪开始执行一次。也就是说,你只要设置了,Scroll就会去执行。
好了接着继续分析:
下面来猜测一下:
程序先从onCreate()方法中进入布局,接着调用setContentView(R.layout.activity_main)这是后就是把布局加载到activity中去,但此时的listview 没有数据并且button也设置为gone,所以在此时的页面没东西显示的,然后执行了异步任务,下载数据。。。
到了onPostExecute()方法时(此时把数据都下载好,并解析存到数据源中):
按照如上的代码,把setAdapter()卸载setOnScrollListener()前面;打印的结果:
①
加载适配器
测量
不等于总
然后把setAdapter()移到//????????的那个位置也就是setAdapter()在setOnScrollListener()后面,打印结果
②
测量
等于总
加载适配器
测量
不等于
②时:
因为-等于总-所以会将button控件设为visiable可见,这时候就会占了底下的位置,也就是说button确实显示出来了(只是时间太短,你没感觉到),很快这时候再加载适配器把数据显示出来,这时候又调用了一次onscoll()方法,发现不等于,马上又把button设为gone了。此时数据已经加载到那里了,再调用requsetLayout()吧button去掉也没用了。(这里关键是button先设为visiable再变为gone这个过程引发的问题)
①时:也能够说的通:先加载适配器,把数据都显示到了listview中,再写setOnScrollListener()方法,此时判断为--不等于--所以button依然是gone的状态。。。
哈哈,到这就差不多完美结束这篇文章了。本来写这篇文章是想简单记录一下出现的这个问题和解决的办法,至于不理解也就算了,但写着写着就忍不住去弄懂背后的原因。本人还是小菜鸟,这篇文章肯定还存在很多漏洞,希望看到的能够指出来!
花了一晚上的时间,效率低,但挺开心的,毕竟好像是弄明白了这个东东。哈哈。。
</pre><p></p><div style="top: 3571px;"><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; color: rgb(51, 51, 51); font-family: Helvetica, Arial, sans-serif; line-height: 19.44px;">可是,我们会发现,当运行程序时,listview明明没有滚动,那为什么系统会调用onScroll方法呢?(补充:此时onScrollStateChanged并不会调用)</p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; color: rgb(51, 51, 51); font-family: Helvetica, Arial, sans-serif; line-height: 19.44px;">我们先看setOnScrollListener源码:</p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; color: rgb(51, 51, 51); font-family: Helvetica, Arial, sans-serif; line-height: 19.44px;"></p><pre class="java" style="margin-top: 0px; margin-bottom: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 19.44px;">public void setOnScrollListener(OnScrollListener l) {
mOnScrollListener = l;
invokeOnItemScrollListener();
}
setOnScrollListener里面调用了invokeOnItemScrollListener()方法,接着看该方法源码:
void invokeOnItemScrollListener() {
if (mFastScroller != null) {
mFastScroller.onScroll(mFirstPosition, getChildCount(), mItemCount);
}
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);//这里调用onScroll,一切真相大白了。
}
onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
}