0
点赞
收藏
分享

微信扫一扫

Android开发实战《智慧北京》——4.WebView的使用


文章目录

  • ​​1.UI框架梳理​​
  • ​​2.滑动显示下拉刷新​​
  • ​​3.通过下拉位置切换刷新状态​​
  • ​​4.根据当前状态刷新界面​​
  • ​​5.设置下拉刷新回调监听 & 刷新数据​​
  • ​​6.更新下拉刷新时间​​
  • ​​7.添加脚布局并隐藏​​
  • ​​8.监听滑动到底事件显示加载中布局​​
  • ​​9.分页加载更多数据​​
  • ​​10.本地记录已读未读状态​​
  • ​​11.根据已读未读状态刷新页面​​
  • ​​12.新闻详情页布局开发​​
  • ​​13.加载新闻网页 & 显示进度条​​

1.UI框架梳理

项目开发到现在,基本上已经完成了大体的功能。不过由于开发流程过于繁琐,这里就按照执行顺序专门列出一个小节来整理一下当前UI框架:

  1. SplashActivity,闪屏页面
  2. GuideActivity,引导页面,第一次打开时会弹出,后续弹出后会读取PreferencedShared中的数据,便不会再弹出
  3. MainActivity,主页面,后续的所有页面均在此处开发
  1. BaseFragment,主碎片,后续碎片均继承这个类
  2. LeftMenuFragment,侧边栏碎片
  3. ContentFragment,内容详情碎片
  1. BasePaper,详情页的基类
  2. HomePaper,“首页”详情页
  3. NewsCenterPaper,“新闻中心”详情页
  1. BaseMenuDetailPaper,侧边栏详情页的基类
  2. NewsMenuDetailPaper,“新闻”侧边栏详情页
  1. TabDetailPaper,显示新闻具体内容的页面
  2. TopNewsViewPaper,显示头条数据具体内容的页面
  3. RefreshListView,显示下拉刷新效果的页面
  1. TopicMenuDetailPaper,“专题”侧边栏详情页
  2. PhotosMenuDetailPaper,“组图”侧边栏详情页
  3. InteractMenuDetailPaper,“互动”侧边栏详情页
  1. SmartServicePaper,“智慧服务”详情页
  2. GovAffairsPaper,“政务”详情页
  3. SettingPaper,“设置”详情页
  4. NoScrollViewPaper,禁用侧边栏的详情页

每个单体页面的实现不算很难,难点在于三层ViewPager的嵌套,以及View事件分发时需要让父类不要拦截子类的滑动事件,否则会出现滑动冲突的问题

2.滑动显示下拉刷新

在上一篇博客中我们实现了刷新效果的显示,现在就需要完善下拉刷新的逻辑,即刷新的同时也更新页面的数据

修改RefreshListView,重写onTouchEvent()方法,用于手势判断,代码如下:

package com.example.zhbj.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ListView;

import com.example.zhbj.R;

/**
* 下拉刷新ListView
*/
public class RefreshListView extends ListView {

/**
* 当前布局的高度
*/
private int mMeasuredHeight;

/**
* HeadView对象
*/
private View mHeadView;

/**
* 起始的Y坐标
*/
private int startY = -1;

/**
* 结束的Y坐标
*/
private int endY;

/**
* Y坐标轴的偏移量
*/
private int dY;

public RefreshListView(Context context) {
this(context, null);
}

public RefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}

public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHeaderView();
}

/**
* 初始化头布局
*/
private void initHeaderView(){
mHeadView = View.inflate(getContext(),R.layout.pull_to_refresh_header,null);
addHeaderView(mHeadView); // 给ListView添加头布局

/*
隐藏头布局
1.获取当前头布局高度
2.设置负paddingTop,布局就会向上走
*/
// int height = mHeadView.getHeight(); 不能这样获取宽高,因为没有绘制完毕
mHeadView.measure(0,0); // 手动测量,宽高传0表示不参与具体宽高的设定,全由系统底层决定
mMeasuredHeight = mHeadView.getMeasuredHeight(); // 获取测量后的高度
mHeadView.setPadding(0,-mMeasuredHeight,0,0);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1){
// 没有获取到按下的事件(按住头条新闻滑动时,按下事件被ViewPager消费了)
startY = (int) ev.getY(); // 重新获取起点的位置
}
endY = (int) ev.getY();
dY = endY - startY;
int firstVisiblePosition = this.getFirstVisiblePosition(); // 当前显示的第一个item的位置
if (dY > 0 && firstVisiblePosition == 0){
// 下拉动作 & 当前在ListView的顶部
int padding = -mMeasuredHeight + dY;
mHeadView.setPadding(0,padding,0,0);
return true; // 消费此事件,处理下拉刷新控件的滑动,不需要ListView原生效果参与
}
break;
case MotionEvent.ACTION_UP:

break;
default:
break;
}
return super.onTouchEvent(ev); // 要返回super,方便ListView原生滑动处理
}
}

3.通过下拉位置切换刷新状态

我们已经完成了下拉刷新的动画,而一般应用在刷新时会改变文字,我们现在需要实现这个效果

  1. 修改RefreshListView,重写onTouchEvent()方法,用于判断三种不同的下拉刷新状态,代码如下:

package com.example.zhbj.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ListView;

import com.example.zhbj.R;

/**
* 下拉刷新ListView
*/
public class RefreshListView extends ListView {

/**
* 当前布局的高度
*/
private int mMeasuredHeight;

/**
* HeadView对象
*/
private View mHeadView;

/**
* 起始的Y坐标
*/
private int startY = -1;

/**
* 结束的Y坐标
*/
private int endY;

/**
* Y坐标轴的偏移量
*/
private int dY;

/**
* 下拉刷新状态标志位
*/
private static final int STATE_PULL_TO_REFRESH = 0;

/**
* 松开刷新状态标志位
*/
private static final int STATE_RELEASE_TO_REFRESH = 1;

/**
* 正在刷新状态标志位
*/
private static final int STATE_REFRESHING = 2;

/**
* 当前状态的标志位,默认为松开刷新
*/
private int mCurrentState = STATE_PULL_TO_REFRESH;

public RefreshListView(Context context) {
this(context, null);
}

public RefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}

public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHeaderView();
}

/**
* 初始化头布局
*/
private void initHeaderView(){
mHeadView = View.inflate(getContext(),R.layout.pull_to_refresh_header,null);
addHeaderView(mHeadView); // 给ListView添加头布局

/*
隐藏头布局
1.获取当前头布局高度
2.设置负paddingTop,布局就会向上走
*/
// int height = mHeadView.getHeight(); 不能这样获取宽高,因为没有绘制完毕
mHeadView.measure(0,0); // 手动测量,宽高传0表示不参与具体宽高的设定,全由系统底层决定
mMeasuredHeight = mHeadView.getMeasuredHeight(); // 获取测量后的高度
mHeadView.setPadding(0,-mMeasuredHeight,0,0);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1){
// 没有获取到按下的事件(按住头条新闻滑动时,按下事件被ViewPager消费了)
startY = (int) ev.getY(); // 重新获取起点的位置
}
endY = (int) ev.getY();
dY = endY - startY;
int firstVisiblePosition = this.getFirstVisiblePosition(); // 当前显示的第一个item的位置
if (dY > 0 && firstVisiblePosition == 0){
// 下拉动作 & 当前在ListView的顶部
int padding = -mMeasuredHeight + dY;
if (padding > 0){
// 切换到松开刷新状态
mCurrentState = STATE_RELEASE_TO_REFRESH;
}
mHeadView.setPadding(0,padding,0,0);
return true; // 消费此事件,处理下拉刷新控件的滑动,不需要ListView原生效果参与
}
break;
case MotionEvent.ACTION_UP:

break;
default:
break;
}
return super.onTouchEvent(ev); // 要返回super,方便ListView原生滑动处理
}
}

  1. 修改RefreshListView,添加refreshState()方法,用于刷新当前状态,代码如下:

package com.example.zhbj.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ListView;

import com.example.zhbj.R;

/**
* 下拉刷新ListView
*/
public class RefreshListView extends ListView {

/**
* 当前布局的高度
*/
private int mMeasuredHeight;

/**
* HeadView对象
*/
private View mHeadView;

/**
* 起始的Y坐标
*/
private int startY = -1;

/**
* 结束的Y坐标
*/
private int endY;

/**
* Y坐标轴的偏移量
*/
private int dY;

/**
* 下拉刷新状态标志位
*/
private static final int STATE_PULL_TO_REFRESH = 0;

/**
* 松开刷新状态标志位
*/
private static final int STATE_RELEASE_TO_REFRESH = 1;

/**
* 正在刷新状态标志位
*/
private static final int STATE_REFRESHING = 2;

/**
* 当前状态的标志位,默认为松开刷新
*/
private int mCurrentState = STATE_PULL_TO_REFRESH;

public RefreshListView(Context context) {
this(context, null);
}

public RefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}

public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHeaderView();
}

/**
* 初始化头布局
*/
private void initHeaderView(){
mHeadView = View.inflate(getContext(),R.layout.pull_to_refresh_header,null);
addHeaderView(mHeadView); // 给ListView添加头布局

/*
隐藏头布局
1.获取当前头布局高度
2.设置负paddingTop,布局就会向上走
*/
// int height = mHeadView.getHeight(); 不能这样获取宽高,因为没有绘制完毕
mHeadView.measure(0,0); // 手动测量,宽高传0表示不参与具体宽高的设定,全由系统底层决定
mMeasuredHeight = mHeadView.getMeasuredHeight(); // 获取测量后的高度
mHeadView.setPadding(0,-mMeasuredHeight,0,0);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1){
// 没有获取到按下的事件(按住头条新闻滑动时,按下事件被ViewPager消费了)
startY = (int) ev.getY(); // 重新获取起点的位置
}
endY = (int) ev.getY();
dY = endY - startY;
int firstVisiblePosition = this.getFirstVisiblePosition(); // 当前显示的第一个item的位置
if (dY > 0 && firstVisiblePosition == 0){
// 下拉动作 & 当前在ListView的顶部
int padding = -mMeasuredHeight + dY;
if (padding > 0 && mCurrentState != STATE_RELEASE_TO_REFRESH){
// 切换到松开刷新状态
mCurrentState = STATE_RELEASE_TO_REFRESH;
refreshState();
}else if (padding <= 0 && mCurrentState != STATE_PULL_TO_REFRESH){
// 切换到下拉刷新状态
mCurrentState = STATE_PULL_TO_REFRESH;
refreshState();
}
// 通过修改padding来设置当前刷新控件的最新位置
mHeadView.setPadding(0,padding,0,0);
return true; // 消费此事件,处理下拉刷新控件的滑动,不需要ListView原生效果参与
}
break;
case MotionEvent.ACTION_UP:
startY = -1; // 起始坐标归0
if (mCurrentState == STATE_RELEASE_TO_REFRESH){
// 切换成正在刷新
mCurrentState = STATE_REFRESHING;
refreshState();
}else if (mCurrentState == STATE_PULL_TO_REFRESH){
// 隐藏刷新控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);
}
break;
default:
break;
}
return super.onTouchEvent(ev); // 要返回super,方便ListView原生滑动处理
}

/**
* 根据当前状态来刷新界面
*/
private void refreshState() {
}
}

4.根据当前状态刷新界面

当状态可以根据手势判断来进行切换后,现在就需要根据当前状态来刷新界面。

  1. 修改RefreshListView,完善refreshState()方法,用于刷新当前状态,代码如下:

package com.example.zhbj.view;

import android.content.Context;
import android.media.Image;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.zhbj.R;

/**
* 下拉刷新ListView
*/
public class RefreshListView extends ListView {

/**
* 当前布局的高度
*/
private int mMeasuredHeight;

/**
* HeadView对象
*/
private View mHeadView;

/**
* 起始的Y坐标
*/
private int startY = -1;

/**
* 结束的Y坐标
*/
private int endY;

/**
* Y坐标轴的偏移量
*/
private int dY;

/**
* 下拉刷新状态标志位
*/
private static final int STATE_PULL_TO_REFRESH = 0;

/**
* 松开刷新状态标志位
*/
private static final int STATE_RELEASE_TO_REFRESH = 1;

/**
* 正在刷新状态标志位
*/
private static final int STATE_REFRESHING = 2;

/**
* 当前状态的标志位,默认为松开刷新
*/
private int mCurrentState = STATE_PULL_TO_REFRESH;

/**
* 状态文本组件
*/
private TextView tvState;

/**
* 时间文本组件
*/
private TextView tvTime;

/**
* 箭头图标
*/
private ImageView ivArrow;

/**
* 加载条组件
*/
private ProgressBar pbLoading;

public RefreshListView(Context context) {
this(context, null);
}

public RefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}

public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHeaderView();
}

/**
* 初始化头布局
*/
private void initHeaderView(){
mHeadView = View.inflate(getContext(),R.layout.pull_to_refresh_header,null);
addHeaderView(mHeadView); // 给ListView添加头布局

tvState = (TextView) mHeadView.findViewById(R.id.tv_state);
tvTime = (TextView) mHeadView.findViewById(R.id.tv_time2);
ivArrow = (ImageView) mHeadView.findViewById(R.id.iv_arrow);
pbLoading = (ProgressBar) mHeadView.findViewById(R.id.pb_loading);
/*
隐藏头布局
1.获取当前头布局高度
2.设置负paddingTop,布局就会向上走
*/
// int height = mHeadView.getHeight(); 不能这样获取宽高,因为没有绘制完毕
mHeadView.measure(0,0); // 手动测量,宽高传0表示不参与具体宽高的设定,全由系统底层决定
mMeasuredHeight = mHeadView.getMeasuredHeight(); // 获取测量后的高度
mHeadView.setPadding(0,-mMeasuredHeight,0,0);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1){
// 没有获取到按下的事件(按住头条新闻滑动时,按下事件被ViewPager消费了)
startY = (int) ev.getY(); // 重新获取起点的位置
}
endY = (int) ev.getY();
dY = endY - startY;
int firstVisiblePosition = this.getFirstVisiblePosition(); // 当前显示的第一个item的位置
if (dY > 0 && firstVisiblePosition == 0){
// 下拉动作 & 当前在ListView的顶部
int padding = -mMeasuredHeight + dY;
if (padding > 0 && mCurrentState != STATE_RELEASE_TO_REFRESH){
// 切换到松开刷新状态
mCurrentState = STATE_RELEASE_TO_REFRESH;
refreshState();
}else if (padding <= 0 && mCurrentState != STATE_PULL_TO_REFRESH){
// 切换到下拉刷新状态
mCurrentState = STATE_PULL_TO_REFRESH;
refreshState();
}
// 通过修改padding来设置当前刷新控件的最新位置
mHeadView.setPadding(0,padding,0,0);
return true; // 消费此事件,处理下拉刷新控件的滑动,不需要ListView原生效果参与
}
break;
case MotionEvent.ACTION_UP:
startY = -1; // 起始坐标归0
if (mCurrentState == STATE_RELEASE_TO_REFRESH){
// 切换成正在刷新
mCurrentState = STATE_REFRESHING;
refreshState();
}else if (mCurrentState == STATE_PULL_TO_REFRESH){
// 隐藏刷新控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);
}
break;
default:
break;
}
return super.onTouchEvent(ev); // 要返回super,方便ListView原生滑动处理
}

/**
* 根据当前状态来刷新界面
*/
private void refreshState() {
switch (mCurrentState){
case STATE_PULL_TO_REFRESH:
tvState.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
break;
case STATE_RELEASE_TO_REFRESH:
tvState.setText("松开刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
break;
case STATE_REFRESHING:
tvState.setText("正在刷新");
pbLoading.setVisibility(View.VISIBLE);
ivArrow.setVisibility(View.INVISIBLE);
break;
default:
break;
}
}
}

  1. 修改RefreshListView,添加initArrowAnim()方法,用于完善箭头的动画,代码如下:

package com.example.zhbj.view;

import android.content.Context;
import android.media.Image;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.zhbj.R;

/**
* 下拉刷新ListView
*/
public class RefreshListView extends ListView {

/**
* 当前布局的高度
*/
private int mMeasuredHeight;

/**
* HeadView对象
*/
private View mHeadView;

/**
* 起始的Y坐标
*/
private int startY = -1;

/**
* 结束的Y坐标
*/
private int endY;

/**
* Y坐标轴的偏移量
*/
private int dY;

/**
* 下拉刷新状态标志位
*/
private static final int STATE_PULL_TO_REFRESH = 0;

/**
* 松开刷新状态标志位
*/
private static final int STATE_RELEASE_TO_REFRESH = 1;

/**
* 正在刷新状态标志位
*/
private static final int STATE_REFRESHING = 2;

/**
* 当前状态的标志位,默认为松开刷新
*/
private int mCurrentState = STATE_PULL_TO_REFRESH;

/**
* 状态文本组件
*/
private TextView tvState;

/**
* 时间文本组件
*/
private TextView tvTime;

/**
* 箭头图标
*/
private ImageView ivArrow;

/**
* 加载条组件
*/
private ProgressBar pbLoading;

/**
* 箭头向上的动画对象
*/
private RotateAnimation animUp;

/**
* 箭头向下的动画对象
*/
private RotateAnimation animDown;

public RefreshListView(Context context) {
this(context, null);
}

public RefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}

public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHeaderView();
}

/**
* 初始化头布局
*/
private void initHeaderView(){
mHeadView = View.inflate(getContext(),R.layout.pull_to_refresh_header,null);
addHeaderView(mHeadView); // 给ListView添加头布局

tvState = (TextView) mHeadView.findViewById(R.id.tv_state);
tvTime = (TextView) mHeadView.findViewById(R.id.tv_time2);
ivArrow = (ImageView) mHeadView.findViewById(R.id.iv_arrow);
pbLoading = (ProgressBar) mHeadView.findViewById(R.id.pb_loading);
/*
隐藏头布局
1.获取当前头布局高度
2.设置负paddingTop,布局就会向上走
*/
// int height = mHeadView.getHeight(); 不能这样获取宽高,因为没有绘制完毕
mHeadView.measure(0,0); // 手动测量,宽高传0表示不参与具体宽高的设定,全由系统底层决定
mMeasuredHeight = mHeadView.getMeasuredHeight(); // 获取测量后的高度
mHeadView.setPadding(0,-mMeasuredHeight,0,0);

initArrowAnim();
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1){
// 没有获取到按下的事件(按住头条新闻滑动时,按下事件被ViewPager消费了)
startY = (int) ev.getY(); // 重新获取起点的位置
}
endY = (int) ev.getY();
dY = endY - startY;
int firstVisiblePosition = this.getFirstVisiblePosition(); // 当前显示的第一个item的位置
if (dY > 0 && firstVisiblePosition == 0){
// 下拉动作 & 当前在ListView的顶部
int padding = -mMeasuredHeight + dY;
if (padding > 0 && mCurrentState != STATE_RELEASE_TO_REFRESH){
// 切换到松开刷新状态
mCurrentState = STATE_RELEASE_TO_REFRESH;
refreshState();
}else if (padding <= 0 && mCurrentState != STATE_PULL_TO_REFRESH){
// 切换到下拉刷新状态
mCurrentState = STATE_PULL_TO_REFRESH;
refreshState();
}
// 通过修改padding来设置当前刷新控件的最新位置
mHeadView.setPadding(0,padding,0,0);
return true; // 消费此事件,处理下拉刷新控件的滑动,不需要ListView原生效果参与
}
break;
case MotionEvent.ACTION_UP:
startY = -1; // 起始坐标归0
if (mCurrentState == STATE_RELEASE_TO_REFRESH){
// 切换成正在刷新
mCurrentState = STATE_REFRESHING;
refreshState();
}else if (mCurrentState == STATE_PULL_TO_REFRESH){
// 隐藏刷新控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);
}
break;
default:
break;
}
return super.onTouchEvent(ev); // 要返回super,方便ListView原生滑动处理
}

/**
* 初始化箭头动画
*/
private void initArrowAnim(){
animUp = new RotateAnimation(0,-180, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animUp.setDuration(300);
animUp.setFillAfter(true); // 保持住动画结束的状态

animDown = new RotateAnimation(-180,0, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animDown.setDuration(300);
animDown.setFillAfter(true); // 保持住动画结束的状态
}

/**
* 根据当前状态来刷新界面
*/
private void refreshState() {
switch (mCurrentState){
case STATE_PULL_TO_REFRESH:
tvState.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
break;
case STATE_RELEASE_TO_REFRESH:
tvState.setText("松开刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
break;
case STATE_REFRESHING:
tvState.setText("正在刷新");
pbLoading.setVisibility(View.VISIBLE);
ivArrow.setVisibility(View.INVISIBLE);
break;
default:
break;
}
}
}

  1. 修改RefreshListView,进一步完善refreshState()方法,用于让箭头改变状态时出现动画,代码如下:

package com.example.zhbj.view;

import android.content.Context;
import android.media.Image;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.zhbj.R;

/**
* 下拉刷新ListView
*/
public class RefreshListView extends ListView {

/**
* 当前布局的高度
*/
private int mMeasuredHeight;

/**
* HeadView对象
*/
private View mHeadView;

/**
* 起始的Y坐标
*/
private int startY = -1;

/**
* 结束的Y坐标
*/
private int endY;

/**
* Y坐标轴的偏移量
*/
private int dY;

/**
* 下拉刷新状态标志位
*/
private static final int STATE_PULL_TO_REFRESH = 0;

/**
* 松开刷新状态标志位
*/
private static final int STATE_RELEASE_TO_REFRESH = 1;

/**
* 正在刷新状态标志位
*/
private static final int STATE_REFRESHING = 2;

/**
* 当前状态的标志位,默认为松开刷新
*/
private int mCurrentState = STATE_PULL_TO_REFRESH;

/**
* 状态文本组件
*/
private TextView tvState;

/**
* 时间文本组件
*/
private TextView tvTime;

/**
* 箭头图标
*/
private ImageView ivArrow;

/**
* 加载条组件
*/
private ProgressBar pbLoading;

/**
* 箭头向上的动画对象
*/
private RotateAnimation animUp;

/**
* 箭头向下的动画对象
*/
private RotateAnimation animDown;

public RefreshListView(Context context) {
this(context, null);
}

public RefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}

public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHeaderView();
}

/**
* 初始化头布局
*/
private void initHeaderView(){
mHeadView = View.inflate(getContext(),R.layout.pull_to_refresh_header,null);
addHeaderView(mHeadView); // 给ListView添加头布局

tvState = (TextView) mHeadView.findViewById(R.id.tv_state);
tvTime = (TextView) mHeadView.findViewById(R.id.tv_time2);
ivArrow = (ImageView) mHeadView.findViewById(R.id.iv_arrow);
pbLoading = (ProgressBar) mHeadView.findViewById(R.id.pb_loading);
/*
隐藏头布局
1.获取当前头布局高度
2.设置负paddingTop,布局就会向上走
*/
// int height = mHeadView.getHeight(); 不能这样获取宽高,因为没有绘制完毕
mHeadView.measure(0,0); // 手动测量,宽高传0表示不参与具体宽高的设定,全由系统底层决定
mMeasuredHeight = mHeadView.getMeasuredHeight(); // 获取测量后的高度
mHeadView.setPadding(0,-mMeasuredHeight,0,0);

initArrowAnim();
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1){
// 没有获取到按下的事件(按住头条新闻滑动时,按下事件被ViewPager消费了)
startY = (int) ev.getY(); // 重新获取起点的位置
}
endY = (int) ev.getY();
dY = endY - startY;

// 如果正在刷新,就什么都不做
if (mCurrentState == STATE_REFRESHING){
break;
}

int firstVisiblePosition = this.getFirstVisiblePosition(); // 当前显示的第一个item的位置
if (dY > 0 && firstVisiblePosition == 0){
// 下拉动作 & 当前在ListView的顶部
int padding = -mMeasuredHeight + dY;
if (padding > 0 && mCurrentState != STATE_RELEASE_TO_REFRESH){
// 切换到松开刷新状态
mCurrentState = STATE_RELEASE_TO_REFRESH;
refreshState();
}else if (padding <= 0 && mCurrentState != STATE_PULL_TO_REFRESH){
// 切换到下拉刷新状态
mCurrentState = STATE_PULL_TO_REFRESH;
refreshState();
}
// 通过修改padding来设置当前刷新控件的最新位置
mHeadView.setPadding(0,padding,0,0);
return true; // 消费此事件,处理下拉刷新控件的滑动,不需要ListView原生效果参与
}
break;
case MotionEvent.ACTION_UP:
startY = -1; // 起始坐标归0
if (mCurrentState == STATE_RELEASE_TO_REFRESH){
// 切换成正在刷新
mCurrentState = STATE_REFRESHING;
// 完整显示刷新控件
mHeadView.setPadding(0,0,0,0);
refreshState();
}else if (mCurrentState == STATE_PULL_TO_REFRESH){
// 隐藏刷新控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);
}
break;
default:
break;
}
return super.onTouchEvent(ev); // 要返回super,方便ListView原生滑动处理
}

/**
* 初始化箭头动画
*/
private void initArrowAnim(){
animUp = new RotateAnimation(0,-180, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animUp.setDuration(300);
animUp.setFillAfter(true); // 保持住动画结束的状态

animDown = new RotateAnimation(-180,0, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animDown.setDuration(300);
animDown.setFillAfter(true); // 保持住动画结束的状态
}

/**
* 根据当前状态来刷新界面
*/
private void refreshState() {
switch (mCurrentState){
case STATE_PULL_TO_REFRESH:
tvState.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
ivArrow.setAnimation(animDown);
break;
case STATE_RELEASE_TO_REFRESH:
tvState.setText("松开刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
ivArrow.setAnimation(animUp);
break;
case STATE_REFRESHING:
tvState.setText("正在刷新");
pbLoading.setVisibility(View.VISIBLE);
ivArrow.clearAnimation(); // 清理动画之后才能隐藏
ivArrow.setVisibility(View.INVISIBLE);
break;
default:
break;
}
}
}

5.设置下拉刷新回调监听 & 刷新数据

我们实现了刷新的效果,现在就需要实现在下拉刷新的时候刷新数据。

  1. 修改RefreshListView,添加OnRefreshListener接口,代码如下:

package com.example.zhbj.view;

import android.content.Context;
import android.media.Image;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.zhbj.R;

/**
* 下拉刷新ListView
*/
public class RefreshListView extends ListView {

/**
* 当前布局的高度
*/
private int mMeasuredHeight;

/**
* HeadView对象
*/
private View mHeadView;

/**
* 起始的Y坐标
*/
private int startY = -1;

/**
* 结束的Y坐标
*/
private int endY;

/**
* Y坐标轴的偏移量
*/
private int dY;

/**
* 下拉刷新状态标志位
*/
private static final int STATE_PULL_TO_REFRESH = 0;

/**
* 松开刷新状态标志位
*/
private static final int STATE_RELEASE_TO_REFRESH = 1;

/**
* 正在刷新状态标志位
*/
private static final int STATE_REFRESHING = 2;

/**
* 当前状态的标志位,默认为松开刷新
*/
private int mCurrentState = STATE_PULL_TO_REFRESH;

/**
* 状态文本组件
*/
private TextView tvState;

/**
* 时间文本组件
*/
private TextView tvTime;

/**
* 箭头图标
*/
private ImageView ivArrow;

/**
* 加载条组件
*/
private ProgressBar pbLoading;

/**
* 箭头向上的动画对象
*/
private RotateAnimation animUp;

/**
* 箭头向下的动画对象
*/
private RotateAnimation animDown;

/**
* 监听器回调接口
*/
private OnRefreshListener mListener;

public RefreshListView(Context context) {
this(context, null);
}

public RefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}

public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHeaderView();
}

/**
* 初始化头布局
*/
private void initHeaderView(){
mHeadView = View.inflate(getContext(),R.layout.pull_to_refresh_header,null);
addHeaderView(mHeadView); // 给ListView添加头布局

tvState = (TextView) mHeadView.findViewById(R.id.tv_state);
tvTime = (TextView) mHeadView.findViewById(R.id.tv_time2);
ivArrow = (ImageView) mHeadView.findViewById(R.id.iv_arrow);
pbLoading = (ProgressBar) mHeadView.findViewById(R.id.pb_loading);
/*
隐藏头布局
1.获取当前头布局高度
2.设置负paddingTop,布局就会向上走
*/
// int height = mHeadView.getHeight(); 不能这样获取宽高,因为没有绘制完毕
mHeadView.measure(0,0); // 手动测量,宽高传0表示不参与具体宽高的设定,全由系统底层决定
mMeasuredHeight = mHeadView.getMeasuredHeight(); // 获取测量后的高度
mHeadView.setPadding(0,-mMeasuredHeight,0,0);

initArrowAnim();
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1){
// 没有获取到按下的事件(按住头条新闻滑动时,按下事件被ViewPager消费了)
startY = (int) ev.getY(); // 重新获取起点的位置
}
endY = (int) ev.getY();
dY = endY - startY;

// 如果正在刷新,就什么都不做
if (mCurrentState == STATE_REFRESHING){
break;
}

int firstVisiblePosition = this.getFirstVisiblePosition(); // 当前显示的第一个item的位置
if (dY > 0 && firstVisiblePosition == 0){
// 下拉动作 & 当前在ListView的顶部
int padding = -mMeasuredHeight + dY;
if (padding > 0 && mCurrentState != STATE_RELEASE_TO_REFRESH){
// 切换到松开刷新状态
mCurrentState = STATE_RELEASE_TO_REFRESH;
refreshState();
}else if (padding <= 0 && mCurrentState != STATE_PULL_TO_REFRESH){
// 切换到下拉刷新状态
mCurrentState = STATE_PULL_TO_REFRESH;
refreshState();
}
// 通过修改padding来设置当前刷新控件的最新位置
mHeadView.setPadding(0,padding,0,0);
return true; // 消费此事件,处理下拉刷新控件的滑动,不需要ListView原生效果参与
}
break;
case MotionEvent.ACTION_UP:
startY = -1; // 起始坐标归0
if (mCurrentState == STATE_RELEASE_TO_REFRESH){
// 切换成正在刷新
mCurrentState = STATE_REFRESHING;
// 完整显示刷新控件
mHeadView.setPadding(0,0,0,0);
refreshState();
}else if (mCurrentState == STATE_PULL_TO_REFRESH){
// 隐藏刷新控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);
}
break;
default:
break;
}
return super.onTouchEvent(ev); // 要返回super,方便ListView原生滑动处理
}

/**
* 初始化箭头动画
*/
private void initArrowAnim(){
animUp = new RotateAnimation(0,-180, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animUp.setDuration(300);
animUp.setFillAfter(true); // 保持住动画结束的状态

animDown = new RotateAnimation(-180,0, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animDown.setDuration(300);
animDown.setFillAfter(true); // 保持住动画结束的状态
}

/**
* 根据当前状态来刷新界面
*/
private void refreshState() {
switch (mCurrentState){
case STATE_PULL_TO_REFRESH:
tvState.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
ivArrow.setAnimation(animDown);
break;
case STATE_RELEASE_TO_REFRESH:
tvState.setText("松开刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
ivArrow.setAnimation(animUp);
break;
case STATE_REFRESHING:
tvState.setText("正在刷新");
pbLoading.setVisibility(View.VISIBLE);
ivArrow.clearAnimation(); // 清理动画之后才能隐藏
ivArrow.setVisibility(View.INVISIBLE);
// 回调下拉刷新
if (mListener != null){
mListener.onRefresh();
}
break;
default:
break;
}
}
public void setOnRefreshListener(OnRefreshListener listener){
mListener = listener;
}

// 回调接口,通知刷新状态
public interface OnRefreshListener{
// 下拉刷新新的回调
public void onRefresh();
}
}

  1. 修改TabDetailPaper,修改initViews()方法,设置下拉刷新的监听,代码如下:

package com.example.zhbj.base.impl;

import android.app.Activity;
import android.graphics.Color;
import android.support.annotation.NonNull;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.zhbj.R;
import com.example.zhbj.base.BaseMenuDetailPaper;
import com.example.zhbj.domain.NewsMenu;
import com.example.zhbj.domain.NewsTab;
import com.example.zhbj.global.GlobalConstans;
import com.example.zhbj.util.CacheUtil;
import com.example.zhbj.view.RefreshListView;
import com.example.zhbj.view.TopNewsViewPaper;
import com.google.gson.Gson;
import com.lidroid.xutils.BitmapUtils;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest;
import com.lidroid.xutils.view.annotation.ViewInject;
import com.viewpagerindicator.CirclePageIndicator;

import java.util.ArrayList;

/**
* 页签详情页,包含北京、中国、国际等页签
* ViewPagerIndicator
* 1.引入ViewPagerIndicator库
* 2.解决v4冲突,用大的版本覆盖小的版本
* 3.仿照sample中的程序进行拷贝SampleTabsDefault
*/
public class TabDetailPaper extends BaseMenuDetailPaper {

/**
* 当前页签数据
*/
private NewsMenu.NewsTabData newsTabData;

/**
* 文本框对象
*/
// private TextView view;

/**
* ViewPager对象
*/
@ViewInject(R.id.vp_tab_detail)
private TopNewsViewPaper mViewPaper;

/**
* 请求的URL地址
*/
private String mUrl;

/**
* 保存头条新闻图片的集合
*/
private ArrayList<NewsTab.TopNews> mTopNewsList;

/**
* TextView控件实例
*/
@ViewInject(R.id.tv_title2)
private TextView tvTitle;

/**
* Indicator控件实例
*/
@ViewInject(R.id.indicator2)
private CirclePageIndicator mIndicator;

/**
* ListView控件实例
*/
@ViewInject(R.id.lv_list)
private RefreshListView lvList;

/**
* 存储新闻列表的集合
*/
private ArrayList<NewsTab.News> mNewsList;

public TabDetailPaper(Activity activity, NewsMenu.NewsTabData newsTabData) {
super(activity);
this.newsTabData = newsTabData;
mUrl = GlobalConstans.SERVER_URL + newsTabData.url;
}

@Override
public View initViews() {
/*
view = new TextView(mActivity);
view.setTextSize(22);
view.setTextColor(Color.RED);
view.setGravity(Gravity.CENTER); // 居中显示
view.setText("页签");
*/
View view = View.inflate(mActivity,R.layout.paper_tab_detail,null);

// 加载头条新闻的头布局
View headerView = View.inflate(mActivity,R.layout.list_item_header,null);
ViewUtils.inject(this,view);
ViewUtils.inject(this,headerView);
lvList.addHeaderView(headerView); // 给ListView添加头布局

// 设置下拉刷新的监听
lvList.setOnRefreshListener(new RefreshListView.OnRefreshListener() {
@Override
public void onRefresh() {
// 刷新数据
getDataFromServer();
}
});
return view;
}

@Override
public void initData() {
// view.setText(newsTabData.title); // 修改当前布局的数据
String cache = CacheUtil.getCache(mActivity, mUrl);
if (!TextUtils.isEmpty(cache)){
// 有缓存
processData(cache);
}
getDataFromServer();
}

/**
* 从服务器中获取数据
*/
private void getDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpRequest.HttpMethod.GET, mUrl, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
processData(result);
CacheUtil.setCache(mActivity,mUrl,result);

}

@Override
public void onFailure(HttpException e, String s) {
e.printStackTrace();
Toast.makeText(mActivity,s,Toast.LENGTH_SHORT).show();
}
});
}

private void processData(String result) {
Gson gson = new Gson();
NewsTab newsTab = gson.fromJson(result, NewsTab.class);

// 初始化头条新闻数据
mTopNewsList = newsTab.data.topnews;
if (mTopNewsList != null){
mViewPaper.setAdapter(new TopNewsAdapter());
mIndicator.setViewPager(mViewPaper); // 将圆形指示器和viewpager绑定
mIndicator.setSnap(true); // 快照展示方式
mIndicator.onPageSelected(0); // 将圆点位置归零,保证圆点和页面同步
mIndicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i1) {

}

@Override
public void onPageSelected(int i) {
// 更新头条新闻的标题
tvTitle.setText(mTopNewsList.get(i).title);
}

@Override
public void onPageScrollStateChanged(int i) {

}
});

// 初始化首页标题
tvTitle.setText(mTopNewsList.get(0).title);
}

/**
* 初始化新闻列表数据
*/
mNewsList = newsTab.data.news;
if (mNewsList != null){
lvList.setAdapter(new NewsAdapter());
}
}

class TopNewsAdapter extends PagerAdapter{

/**
* xUtils中的BitmapUtils工具类
*/
private BitmapUtils mBitmapUtils;

public TopNewsAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
// 设置加载中的默认图片
mBitmapUtils.configDefaultLoadingImage(R.drawable.pic_item_list_default);
}

@Override
public int getCount() {
return mTopNewsList.size();
}

@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
return view == o;
}

@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
ImageView view = new ImageView(mActivity);
NewsTab.TopNews topNews = mTopNewsList.get(position);
String topimage = topNews.topimage; // 图片的下载链接

view.setScaleType(ImageView.ScaleType.FIT_XY); // 设置缩放模式:宽高匹配窗体
/**
* 1,根据url下载图片
* 2.将图片设置给ImageView
* 3.将图片作成缓存
* 4.避免内存溢出
* 由于工程量巨大,这里使用xUtils中的BitmapUtils里的api来完成这四个逻辑
*/
mBitmapUtils.display(view,topimage);

container.addView(view);
return view;
}

@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object);
}
}

/**
* 新闻列表适配器
*/
class NewsAdapter extends BaseAdapter{

/**
* BitmapUtils工具类
*/
private BitmapUtils mBitmapUtils;

public NewsAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
mBitmapUtils.configDefaultLoadingImage(R.drawable.news_pic_default);
}

@Override
public int getCount() {
return mNewsList.size();
}

@Override
public NewsTab.News getItem(int position) {
return mNewsList.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null){
convertView = View.inflate(mActivity,R.layout.list_item_news,null);
holder = new ViewHolder();
holder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_icon);
holder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title3);
holder.tvTime = (TextView) convertView.findViewById(R.id.tv_time);

convertView.setTag(holder);
}else {
holder = (ViewHolder) convertView.getTag();
}
NewsTab.News info = getItem(position);
holder.tvTitle.setText(info.title);
holder.tvTime.setText(info.pubdate);
mBitmapUtils.display(holder.ivIcon,info.listimage);
return convertView;
}
}

static class ViewHolder{
public ImageView ivIcon;
public TextView tvTitle;
public TextView tvTime;
}

}

  1. 修改RefreshListView,添加onRefreshComplete()方法,表示下拉刷新结束后隐藏组件的逻辑,代码如下:

/**
* 刷新结束,隐藏控件
*/
public void onRefreshComplete(){
// 隐藏控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);
// 所有状态初始化
tvState.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
mCurrentState = STATE_PULL_TO_REFRESH;
}

  1. 修改TabDetailPaper,修改getDataFromServer()方法,调用刚才编写的方法,代码如下:

package com.example.zhbj.base.impl;

import android.app.Activity;
import android.graphics.Color;
import android.support.annotation.NonNull;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.zhbj.R;
import com.example.zhbj.base.BaseMenuDetailPaper;
import com.example.zhbj.domain.NewsMenu;
import com.example.zhbj.domain.NewsTab;
import com.example.zhbj.global.GlobalConstans;
import com.example.zhbj.util.CacheUtil;
import com.example.zhbj.view.RefreshListView;
import com.example.zhbj.view.TopNewsViewPaper;
import com.google.gson.Gson;
import com.lidroid.xutils.BitmapUtils;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest;
import com.lidroid.xutils.view.annotation.ViewInject;
import com.viewpagerindicator.CirclePageIndicator;

import java.util.ArrayList;

/**
* 页签详情页,包含北京、中国、国际等页签
* ViewPagerIndicator
* 1.引入ViewPagerIndicator库
* 2.解决v4冲突,用大的版本覆盖小的版本
* 3.仿照sample中的程序进行拷贝SampleTabsDefault
*/
public class TabDetailPaper extends BaseMenuDetailPaper {

/**
* 当前页签数据
*/
private NewsMenu.NewsTabData newsTabData;

/**
* 文本框对象
*/
// private TextView view;

/**
* ViewPager对象
*/
@ViewInject(R.id.vp_tab_detail)
private TopNewsViewPaper mViewPaper;

/**
* 请求的URL地址
*/
private String mUrl;

/**
* 保存头条新闻图片的集合
*/
private ArrayList<NewsTab.TopNews> mTopNewsList;

/**
* TextView控件实例
*/
@ViewInject(R.id.tv_title2)
private TextView tvTitle;

/**
* Indicator控件实例
*/
@ViewInject(R.id.indicator2)
private CirclePageIndicator mIndicator;

/**
* ListView控件实例
*/
@ViewInject(R.id.lv_list)
private RefreshListView lvList;

/**
* 存储新闻列表的集合
*/
private ArrayList<NewsTab.News> mNewsList;

public TabDetailPaper(Activity activity, NewsMenu.NewsTabData newsTabData) {
super(activity);
this.newsTabData = newsTabData;
mUrl = GlobalConstans.SERVER_URL + newsTabData.url;
}

@Override
public View initViews() {
/*
view = new TextView(mActivity);
view.setTextSize(22);
view.setTextColor(Color.RED);
view.setGravity(Gravity.CENTER); // 居中显示
view.setText("页签");
*/
View view = View.inflate(mActivity,R.layout.paper_tab_detail,null);

// 加载头条新闻的头布局
View headerView = View.inflate(mActivity,R.layout.list_item_header,null);
ViewUtils.inject(this,view);
ViewUtils.inject(this,headerView);
lvList.addHeaderView(headerView); // 给ListView添加头布局

// 设置下拉刷新的监听
lvList.setOnRefreshListener(new RefreshListView.OnRefreshListener() {
@Override
public void onRefresh() {
// 刷新数据
getDataFromServer();
}
});
return view;
}

@Override
public void initData() {
// view.setText(newsTabData.title); // 修改当前布局的数据
String cache = CacheUtil.getCache(mActivity, mUrl);
if (!TextUtils.isEmpty(cache)){
// 有缓存
processData(cache);
}
getDataFromServer();
}

/**
* 从服务器中获取数据
*/
private void getDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpRequest.HttpMethod.GET, mUrl, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
processData(result);
CacheUtil.setCache(mActivity,mUrl,result);

// 隐藏下拉刷新控件
lvList.onRefreshComplete();
}

@Override
public void onFailure(HttpException e, String s) {
e.printStackTrace();
Toast.makeText(mActivity,s,Toast.LENGTH_SHORT).show();

// 隐藏下拉刷新控件
lvList.onRefreshComplete();
}
});
}

private void processData(String result) {
Gson gson = new Gson();
NewsTab newsTab = gson.fromJson(result, NewsTab.class);

// 初始化头条新闻数据
mTopNewsList = newsTab.data.topnews;
if (mTopNewsList != null){
mViewPaper.setAdapter(new TopNewsAdapter());
mIndicator.setViewPager(mViewPaper); // 将圆形指示器和viewpager绑定
mIndicator.setSnap(true); // 快照展示方式
mIndicator.onPageSelected(0); // 将圆点位置归零,保证圆点和页面同步
mIndicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i1) {

}

@Override
public void onPageSelected(int i) {
// 更新头条新闻的标题
tvTitle.setText(mTopNewsList.get(i).title);
}

@Override
public void onPageScrollStateChanged(int i) {

}
});

// 初始化首页标题
tvTitle.setText(mTopNewsList.get(0).title);
}

/**
* 初始化新闻列表数据
*/
mNewsList = newsTab.data.news;
if (mNewsList != null){
lvList.setAdapter(new NewsAdapter());
}
}

class TopNewsAdapter extends PagerAdapter{

/**
* xUtils中的BitmapUtils工具类
*/
private BitmapUtils mBitmapUtils;

public TopNewsAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
// 设置加载中的默认图片
mBitmapUtils.configDefaultLoadingImage(R.drawable.pic_item_list_default);
}

@Override
public int getCount() {
return mTopNewsList.size();
}

@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
return view == o;
}

@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
ImageView view = new ImageView(mActivity);
NewsTab.TopNews topNews = mTopNewsList.get(position);
String topimage = topNews.topimage; // 图片的下载链接

view.setScaleType(ImageView.ScaleType.FIT_XY); // 设置缩放模式:宽高匹配窗体
/**
* 1,根据url下载图片
* 2.将图片设置给ImageView
* 3.将图片作成缓存
* 4.避免内存溢出
* 由于工程量巨大,这里使用xUtils中的BitmapUtils里的api来完成这四个逻辑
*/
mBitmapUtils.display(view,topimage);

container.addView(view);
return view;
}

@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object);
}
}

/**
* 新闻列表适配器
*/
class NewsAdapter extends BaseAdapter{

/**
* BitmapUtils工具类
*/
private BitmapUtils mBitmapUtils;

public NewsAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
mBitmapUtils.configDefaultLoadingImage(R.drawable.news_pic_default);
}

@Override
public int getCount() {
return mNewsList.size();
}

@Override
public NewsTab.News getItem(int position) {
return mNewsList.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null){
convertView = View.inflate(mActivity,R.layout.list_item_news,null);
holder = new ViewHolder();
holder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_icon);
holder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title3);
holder.tvTime = (TextView) convertView.findViewById(R.id.tv_time);

convertView.setTag(holder);
}else {
holder = (ViewHolder) convertView.getTag();
}
NewsTab.News info = getItem(position);
holder.tvTitle.setText(info.title);
holder.tvTime.setText(info.pubdate);
mBitmapUtils.display(holder.ivIcon,info.listimage);
return convertView;
}
}

static class ViewHolder{
public ImageView ivIcon;
public TextView tvTitle;
public TextView tvTime;
}

}

6.更新下拉刷新时间

现在数据已经能够刷新了,现在就需要刷新时间。

  1. 修改RefreshListView,添加setRefreshTime()方法,代码如下:

// 设置刷新时间
private void setRefreshTime(){
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = format.format(new Date());
tvTime.setText(time);
}

  1. 修改RefreshListView,修改onRefreshComplete()方法,调用上面的方法,并且在初始化时也刷新一次时间,代码如下:

package com.example.zhbj.view;

import android.content.Context;
import android.media.Image;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.zhbj.R;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
* 下拉刷新ListView
*/
public class RefreshListView extends ListView {

/**
* 当前布局的高度
*/
private int mMeasuredHeight;

/**
* HeadView对象
*/
private View mHeadView;

/**
* 起始的Y坐标
*/
private int startY = -1;

/**
* 结束的Y坐标
*/
private int endY;

/**
* Y坐标轴的偏移量
*/
private int dY;

/**
* 下拉刷新状态标志位
*/
private static final int STATE_PULL_TO_REFRESH = 0;

/**
* 松开刷新状态标志位
*/
private static final int STATE_RELEASE_TO_REFRESH = 1;

/**
* 正在刷新状态标志位
*/
private static final int STATE_REFRESHING = 2;

/**
* 当前状态的标志位,默认为松开刷新
*/
private int mCurrentState = STATE_PULL_TO_REFRESH;

/**
* 状态文本组件
*/
private TextView tvState;

/**
* 时间文本组件
*/
private TextView tvTime;

/**
* 箭头图标
*/
private ImageView ivArrow;

/**
* 加载条组件
*/
private ProgressBar pbLoading;

/**
* 箭头向上的动画对象
*/
private RotateAnimation animUp;

/**
* 箭头向下的动画对象
*/
private RotateAnimation animDown;

/**
* 监听器回调接口
*/
private OnRefreshListener mListener;

public RefreshListView(Context context) {
this(context, null);
}

public RefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}

public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHeaderView();
}

/**
* 初始化头布局
*/
private void initHeaderView(){
mHeadView = View.inflate(getContext(),R.layout.pull_to_refresh_header,null);
addHeaderView(mHeadView); // 给ListView添加头布局

tvState = (TextView) mHeadView.findViewById(R.id.tv_state);
tvTime = (TextView) mHeadView.findViewById(R.id.tv_time2);
ivArrow = (ImageView) mHeadView.findViewById(R.id.iv_arrow);
pbLoading = (ProgressBar) mHeadView.findViewById(R.id.pb_loading);
/*
隐藏头布局
1.获取当前头布局高度
2.设置负paddingTop,布局就会向上走
*/
// int height = mHeadView.getHeight(); 不能这样获取宽高,因为没有绘制完毕
mHeadView.measure(0,0); // 手动测量,宽高传0表示不参与具体宽高的设定,全由系统底层决定
mMeasuredHeight = mHeadView.getMeasuredHeight(); // 获取测量后的高度
mHeadView.setPadding(0,-mMeasuredHeight,0,0);

initArrowAnim();
setRefreshTime();
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1){
// 没有获取到按下的事件(按住头条新闻滑动时,按下事件被ViewPager消费了)
startY = (int) ev.getY(); // 重新获取起点的位置
}
endY = (int) ev.getY();
dY = endY - startY;

// 如果正在刷新,就什么都不做
if (mCurrentState == STATE_REFRESHING){
break;
}

int firstVisiblePosition = this.getFirstVisiblePosition(); // 当前显示的第一个item的位置
if (dY > 0 && firstVisiblePosition == 0){
// 下拉动作 & 当前在ListView的顶部
int padding = -mMeasuredHeight + dY;
if (padding > 0 && mCurrentState != STATE_RELEASE_TO_REFRESH){
// 切换到松开刷新状态
mCurrentState = STATE_RELEASE_TO_REFRESH;
refreshState();
}else if (padding <= 0 && mCurrentState != STATE_PULL_TO_REFRESH){
// 切换到下拉刷新状态
mCurrentState = STATE_PULL_TO_REFRESH;
refreshState();
}
// 通过修改padding来设置当前刷新控件的最新位置
mHeadView.setPadding(0,padding,0,0);
return true; // 消费此事件,处理下拉刷新控件的滑动,不需要ListView原生效果参与
}
break;
case MotionEvent.ACTION_UP:
startY = -1; // 起始坐标归0
if (mCurrentState == STATE_RELEASE_TO_REFRESH){
// 切换成正在刷新
mCurrentState = STATE_REFRESHING;
// 完整显示刷新控件
mHeadView.setPadding(0,0,0,0);
refreshState();
}else if (mCurrentState == STATE_PULL_TO_REFRESH){
// 隐藏刷新控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);
}
break;
default:
break;
}
return super.onTouchEvent(ev); // 要返回super,方便ListView原生滑动处理
}

/**
* 初始化箭头动画
*/
private void initArrowAnim(){
animUp = new RotateAnimation(0,-180, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animUp.setDuration(300);
animUp.setFillAfter(true); // 保持住动画结束的状态

animDown = new RotateAnimation(-180,0, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animDown.setDuration(300);
animDown.setFillAfter(true); // 保持住动画结束的状态
}

/**
* 根据当前状态来刷新界面
*/
private void refreshState() {
switch (mCurrentState){
case STATE_PULL_TO_REFRESH:
tvState.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
ivArrow.setAnimation(animDown);
break;
case STATE_RELEASE_TO_REFRESH:
tvState.setText("松开刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
ivArrow.setAnimation(animUp);
break;
case STATE_REFRESHING:
tvState.setText("正在刷新");
pbLoading.setVisibility(View.VISIBLE);
ivArrow.clearAnimation(); // 清理动画之后才能隐藏
ivArrow.setVisibility(View.INVISIBLE);
// 回调下拉刷新
if (mListener != null){
mListener.onRefresh();
}
break;
default:
break;
}
}

/**
* 刷新结束,隐藏控件
*/
public void onRefreshComplete(){
// 隐藏控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);

// 所有状态初始化
tvState.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
mCurrentState = STATE_PULL_TO_REFRESH;

// 更新刷新时间
setRefreshTime();
}

public void setOnRefreshListener(OnRefreshListener listener){
mListener = listener;
}

// 回调接口,通知刷新状态
public interface OnRefreshListener{
// 下拉刷新新的回调
public void onRefresh();
}

// 设置刷新时间
private void setRefreshTime(){
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = format.format(new Date());
tvTime.setText(time);
}
}

7.添加脚布局并隐藏

现在添加完刷新逻辑后,就需要添加另一种效果:在下拉列表时会出现类似于刷新分页的效果,借助ListView的api,我们现在开始实现。

  1. 新建一个布局文件pull_to_refresh_foot.xml,作为ListView的脚步局,代码如下:

<?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:padding="5dp"
android:gravity="center"
android:orientation="horizontal">

<ProgressBar
android:id="@+id/pb_loading2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateDrawable="@drawable/shape_custom_progress"
android:layout_gravity="center"/>

<TextView
android:id="@+id/tv_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="加载中..."
android:layout_marginLeft="5dp"
android:textColor="#f00"
android:textSize="18sp"/>
</LinearLayout>

  1. 修改RefreshListView,添加initFooterView()方法,表示初始化脚布局,并且在初始化方法中添加这个api,代码如下:

package com.example.zhbj.view;

import android.content.Context;
import android.media.Image;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.zhbj.R;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
* 下拉刷新ListView
*/
public class RefreshListView extends ListView {

/**
* 当前布局的高度
*/
private int mMeasuredHeight;

/**
* 头布局对象
*/
private View mHeadView;

/**
* 脚布局对象
*/
private View mFootView;

/**
* 起始的Y坐标
*/
private int startY = -1;

/**
* 结束的Y坐标
*/
private int endY;

/**
* Y坐标轴的偏移量
*/
private int dY;

/**
* 下拉刷新状态标志位
*/
private static final int STATE_PULL_TO_REFRESH = 0;

/**
* 松开刷新状态标志位
*/
private static final int STATE_RELEASE_TO_REFRESH = 1;

/**
* 正在刷新状态标志位
*/
private static final int STATE_REFRESHING = 2;

/**
* 当前状态的标志位,默认为松开刷新
*/
private int mCurrentState = STATE_PULL_TO_REFRESH;

/**
* 状态文本组件
*/
private TextView tvState;

/**
* 时间文本组件
*/
private TextView tvTime;

/**
* 箭头图标
*/
private ImageView ivArrow;

/**
* 加载条组件
*/
private ProgressBar pbLoading;

/**
* 箭头向上的动画对象
*/
private RotateAnimation animUp;

/**
* 箭头向下的动画对象
*/
private RotateAnimation animDown;

/**
* 监听器回调接口
*/
private OnRefreshListener mListener;

public RefreshListView(Context context) {
this(context, null);
}

public RefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}

public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHeaderView();
initFooterView();
}

/**
* 初始化头布局
*/
private void initHeaderView(){
mHeadView = View.inflate(getContext(),R.layout.pull_to_refresh_header,null);
addHeaderView(mHeadView); // 给ListView添加头布局

tvState = (TextView) mHeadView.findViewById(R.id.tv_state);
tvTime = (TextView) mHeadView.findViewById(R.id.tv_time2);
ivArrow = (ImageView) mHeadView.findViewById(R.id.iv_arrow);
pbLoading = (ProgressBar) mHeadView.findViewById(R.id.pb_loading);
/*
隐藏头布局
1.获取当前头布局高度
2.设置负paddingTop,布局就会向上走
*/
// int height = mHeadView.getHeight(); 不能这样获取宽高,因为没有绘制完毕
mHeadView.measure(0,0); // 手动测量,宽高传0表示不参与具体宽高的设定,全由系统底层决定
mMeasuredHeight = mHeadView.getMeasuredHeight(); // 获取测量后的高度
mHeadView.setPadding(0,-mMeasuredHeight,0,0);

initArrowAnim();
setRefreshTime();
}

/**
* 初始化脚布局
*/
private void initFooterView(){
mFootView = View.inflate(getContext(),R.layout.pull_to_refresh_foot,null);
addFooterView(mFootView);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1){
// 没有获取到按下的事件(按住头条新闻滑动时,按下事件被ViewPager消费了)
startY = (int) ev.getY(); // 重新获取起点的位置
}
endY = (int) ev.getY();
dY = endY - startY;

// 如果正在刷新,就什么都不做
if (mCurrentState == STATE_REFRESHING){
break;
}

int firstVisiblePosition = this.getFirstVisiblePosition(); // 当前显示的第一个item的位置
if (dY > 0 && firstVisiblePosition == 0){
// 下拉动作 & 当前在ListView的顶部
int padding = -mMeasuredHeight + dY;
if (padding > 0 && mCurrentState != STATE_RELEASE_TO_REFRESH){
// 切换到松开刷新状态
mCurrentState = STATE_RELEASE_TO_REFRESH;
refreshState();
}else if (padding <= 0 && mCurrentState != STATE_PULL_TO_REFRESH){
// 切换到下拉刷新状态
mCurrentState = STATE_PULL_TO_REFRESH;
refreshState();
}
// 通过修改padding来设置当前刷新控件的最新位置
mHeadView.setPadding(0,padding,0,0);
return true; // 消费此事件,处理下拉刷新控件的滑动,不需要ListView原生效果参与
}
break;
case MotionEvent.ACTION_UP:
startY = -1; // 起始坐标归0
if (mCurrentState == STATE_RELEASE_TO_REFRESH){
// 切换成正在刷新
mCurrentState = STATE_REFRESHING;
// 完整显示刷新控件
mHeadView.setPadding(0,0,0,0);
refreshState();
}else if (mCurrentState == STATE_PULL_TO_REFRESH){
// 隐藏刷新控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);
}
break;
default:
break;
}
return super.onTouchEvent(ev); // 要返回super,方便ListView原生滑动处理
}

/**
* 初始化箭头动画
*/
private void initArrowAnim(){
animUp = new RotateAnimation(0,-180, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animUp.setDuration(300);
animUp.setFillAfter(true); // 保持住动画结束的状态

animDown = new RotateAnimation(-180,0, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animDown.setDuration(300);
animDown.setFillAfter(true); // 保持住动画结束的状态
}
bLE);
ivArrow.setVisibility(View.VISIBLE);
ivArrow.setAnimation(animDown);
break;
case STATE_RELEASE_TO_REFRESH:
tvState.setText("松开刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
ivArrow.setAnimation(animUp);
break;
case STATE_REFRESHING:
tvState.setText("正在刷新");
pbLoading.setVisibility(View.VISIBLE);
ivArrow.clearAnimation(); // 清理动画之后才能隐藏
ivArrow.setVisibility(View.INVISIBLE);
// 回调下拉刷新
if (mListener != null){
mListener.onRefresh();
}
break;
default:
break;
}
}

/**
* 刷新结束,隐藏控件
*/
public void onRefreshComplete(){
// 隐藏控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);

// 所有状态初始化
tvState.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
mCurrentState = STATE_PULL_TO_REFRESH;

// 更新刷新时间
setRefreshTime();
}

public void setOnRefreshListener(OnRefreshListener listener){
mListener = listener;
}

// 回调接口,通知刷新状态
public interface OnRefreshListener{
// 下拉刷新新的回调
public void onRefresh();
}

// 设置刷新时间
private void setRefreshTime(){
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = format.format(new Date());
tvTime.setText(time);
}
}

8.监听滑动到底事件显示加载中布局

之前添加了脚布局刷新的效果,现在需要实现滑动到底加载数据的逻辑

修改RefreshListView,实现OnScrollListener接口,并且重写onScrollStateChanged()方法中的相应逻辑,代码如下:

package com.example.zhbj.view;

import android.content.Context;
import android.media.Image;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.zhbj.R;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
* 下拉刷新ListView
*/
public class RefreshListView extends ListView implements AbsListView.OnScrollListener {

/**
* 头布局的高度
*/
private int mMeasuredHeight;

/**
* 头布局对象
*/
private View mHeadView;

/**
* 脚布局的高度
*/
private int mMeasuredFootHeight;

/**
* 脚布局对象
*/
private View mFootView;

/**
* 起始的Y坐标
*/
private int startY = -1;

/**
* 结束的Y坐标
*/
private int endY;

/**
* Y坐标轴的偏移量
*/
private int dY;

/**
* 下拉刷新状态标志位
*/
private static final int STATE_PULL_TO_REFRESH = 0;

/**
* 松开刷新状态标志位
*/
private static final int STATE_RELEASE_TO_REFRESH = 1;

/**
* 正在刷新状态标志位
*/
private static final int STATE_REFRESHING = 2;

/**
* 当前状态的标志位,默认为松开刷新
*/
private int mCurrentState = STATE_PULL_TO_REFRESH;

/**
* 状态文本组件
*/
private TextView tvState;

/**
* 时间文本组件
*/
private TextView tvTime;

/**
* 箭头图标
*/
private ImageView ivArrow;

/**
* 加载条组件
*/
private ProgressBar pbLoading;

/**
* 箭头向上的动画对象
*/
private RotateAnimation animUp;

/**
* 箭头向下的动画对象
*/
private RotateAnimation animDown;

/**
* 监听器回调接口
*/
private OnRefreshListener mListener;

public RefreshListView(Context context) {
this(context, null);
}

public RefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}

public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHeaderView();
initFooterView();
}

/**
* 初始化头布局
*/
private void initHeaderView(){
mHeadView = View.inflate(getContext(),R.layout.pull_to_refresh_header,null);
addHeaderView(mHeadView); // 给ListView添加头布局

tvState = (TextView) mHeadView.findViewById(R.id.tv_state);
tvTime = (TextView) mHeadView.findViewById(R.id.tv_time2);
ivArrow = (ImageView) mHeadView.findViewById(R.id.iv_arrow);
pbLoading = (ProgressBar) mHeadView.findViewById(R.id.pb_loading);
/*
隐藏头布局
1.获取当前头布局高度
2.设置负paddingTop,布局就会向上走
*/
// int height = mHeadView.getHeight(); 不能这样获取宽高,因为没有绘制完毕
mHeadView.measure(0,0); // 手动测量,宽高传0表示不参与具体宽高的设定,全由系统底层决定
mMeasuredHeight = mHeadView.getMeasuredHeight(); // 获取测量后的高度
mHeadView.setPadding(0,-mMeasuredHeight,0,0);

initArrowAnim();
setRefreshTime();
}

/**
* 初始化脚布局
*/
private void initFooterView(){
mFootView = View.inflate(getContext(),R.layout.pull_to_refresh_foot,null);
addFooterView(mFootView);

mFootView.measure(0,0);
mMeasuredFootHeight = mFootView.getMeasuredHeight();
// 隐藏脚布局
mFootView.setPadding(0,-mMeasuredFootHeight,0,0);

// 设置滑动监听
setOnScrollListener(this);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1){
// 没有获取到按下的事件(按住头条新闻滑动时,按下事件被ViewPager消费了)
startY = (int) ev.getY(); // 重新获取起点的位置
}
endY = (int) ev.getY();
dY = endY - startY;

// 如果正在刷新,就什么都不做
if (mCurrentState == STATE_REFRESHING){
break;
}

int firstVisiblePosition = this.getFirstVisiblePosition(); // 当前显示的第一个item的位置
if (dY > 0 && firstVisiblePosition == 0){
// 下拉动作 & 当前在ListView的顶部
int padding = -mMeasuredHeight + dY;
if (padding > 0 && mCurrentState != STATE_RELEASE_TO_REFRESH){
// 切换到松开刷新状态
mCurrentState = STATE_RELEASE_TO_REFRESH;
refreshState();
}else if (padding <= 0 && mCurrentState != STATE_PULL_TO_REFRESH){
// 切换到下拉刷新状态
mCurrentState = STATE_PULL_TO_REFRESH;
refreshState();
}
// 通过修改padding来设置当前刷新控件的最新位置
mHeadView.setPadding(0,padding,0,0);
return true; // 消费此事件,处理下拉刷新控件的滑动,不需要ListView原生效果参与
}
break;
case MotionEvent.ACTION_UP:
startY = -1; // 起始坐标归0
if (mCurrentState == STATE_RELEASE_TO_REFRESH){
// 切换成正在刷新
mCurrentState = STATE_REFRESHING;
// 完整显示刷新控件
mHeadView.setPadding(0,0,0,0);
refreshState();
}else if (mCurrentState == STATE_PULL_TO_REFRESH){
// 隐藏刷新控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);
}
break;
default:
break;
}
return super.onTouchEvent(ev); // 要返回super,方便ListView原生滑动处理
}

/**
* 初始化箭头动画
*/
private void initArrowAnim(){
animUp = new RotateAnimation(0,-180, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animUp.setDuration(300);
animUp.setFillAfter(true); // 保持住动画结束的状态

animDown = new RotateAnimation(-180,0, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animDown.setDuration(300);
animDown.setFillAfter(true); // 保持住动画结束的状态
}

/**
* 根据当前状态来刷新界面
*/
private void refreshState() {
switch (mCurrentState){
case STATE_PULL_TO_REFRESH:
tvState.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
ivArrow.setAnimation(animDown);
break;
case STATE_RELEASE_TO_REFRESH:
tvState.setText("松开刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
ivArrow.setAnimation(animUp);
break;
case STATE_REFRESHING:
tvState.setText("正在刷新");
pbLoading.setVisibility(View.VISIBLE);
ivArrow.clearAnimation(); // 清理动画之后才能隐藏
ivArrow.setVisibility(View.INVISIBLE);
// 回调下拉刷新
if (mListener != null){
mListener.onRefresh();
}
break;
default:
break;
}
}

/**
* 刷新结束,隐藏控件
*/
public void onRefreshComplete(){
// 隐藏控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);

// 所有状态初始化
tvState.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
mCurrentState = STATE_PULL_TO_REFRESH;

// 更新刷新时间
setRefreshTime();
}

public void setOnRefreshListener(OnRefreshListener listener){
mListener = listener;
}

/**
* 滑动状态发生变化
* @param view
* @param scrollState
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE){
// 空闲状态
int lastPosition = getLastVisiblePosition(); // 当前显示item的最后一个位置
if (lastPosition == getCount() - 1){
// 显示加载中布局
mFootView.setPadding(0,0,0,0);
setSelection(getCount() - 1); // 显示在最后一个item的位置(展示加载中布局)
}
}
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

}

// 回调接口,通知刷新状态
public interface OnRefreshListener{
// 下拉刷新新的回调
public void onRefresh();
}

// 设置刷新时间
private void setRefreshTime(){
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = format.format(new Date());
tvTime.setText(time);
}
}

9.分页加载更多数据

之前我们实现了将刷新效果显示到脚布局中,现在就需要通过分页来加载更多数据

  1. 修改RefreshListView,在OnRefreshListener接口中添加一个方法onLoadMore(),并且完善相应逻辑,代码如下:

package com.example.zhbj.view;

import android.content.Context;
import android.media.Image;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.zhbj.R;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
* 下拉刷新ListView
*/
public class RefreshListView extends ListView implements AbsListView.OnScrollListener {

/**
* 头布局的高度
*/
private int mMeasuredHeight;

/**
* 头布局对象
*/
private View mHeadView;

/**
* 脚布局的高度
*/
private int mMeasuredFootHeight;

/**
* 脚布局对象
*/
private View mFootView;

/**
* 起始的Y坐标
*/
private int startY = -1;

/**
* 结束的Y坐标
*/
private int endY;

/**
* Y坐标轴的偏移量
*/
private int dY;

/**
* 下拉刷新状态标志位
*/
private static final int STATE_PULL_TO_REFRESH = 0;

/**
* 松开刷新状态标志位
*/
private static final int STATE_RELEASE_TO_REFRESH = 1;

/**
* 正在刷新状态标志位
*/
private static final int STATE_REFRESHING = 2;

/**
* 当前状态的标志位,默认为松开刷新
*/
private int mCurrentState = STATE_PULL_TO_REFRESH;

/**
* 状态文本组件
*/
private TextView tvState;

/**
* 时间文本组件
*/
private TextView tvTime;

/**
* 箭头图标
*/
private ImageView ivArrow;

/**
* 加载条组件
*/
private ProgressBar pbLoading;

/**
* 箭头向上的动画对象
*/
private RotateAnimation animUp;

/**
* 箭头向下的动画对象
*/
private RotateAnimation animDown;

/**
* 监听器回调接口
*/
private OnRefreshListener mListener;

public RefreshListView(Context context) {
this(context, null);
}

public RefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}

public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHeaderView();
initFooterView();
}

/**
* 初始化头布局
*/
private void initHeaderView(){
mHeadView = View.inflate(getContext(),R.layout.pull_to_refresh_header,null);
addHeaderView(mHeadView); // 给ListView添加头布局

tvState = (TextView) mHeadView.findViewById(R.id.tv_state);
tvTime = (TextView) mHeadView.findViewById(R.id.tv_time2);
ivArrow = (ImageView) mHeadView.findViewById(R.id.iv_arrow);
pbLoading = (ProgressBar) mHeadView.findViewById(R.id.pb_loading);
/*
隐藏头布局
1.获取当前头布局高度
2.设置负paddingTop,布局就会向上走
*/
// int height = mHeadView.getHeight(); 不能这样获取宽高,因为没有绘制完毕
mHeadView.measure(0,0); // 手动测量,宽高传0表示不参与具体宽高的设定,全由系统底层决定
mMeasuredHeight = mHeadView.getMeasuredHeight(); // 获取测量后的高度
mHeadView.setPadding(0,-mMeasuredHeight,0,0);

initArrowAnim();
setRefreshTime();
}

/**
* 初始化脚布局
*/
private void initFooterView(){
mFootView = View.inflate(getContext(),R.layout.pull_to_refresh_foot,null);
addFooterView(mFootView);

mFootView.measure(0,0);
mMeasuredFootHeight = mFootView.getMeasuredHeight();
// 隐藏脚布局
mFootView.setPadding(0,-mMeasuredFootHeight,0,0);

// 设置滑动监听
setOnScrollListener(this);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1){
// 没有获取到按下的事件(按住头条新闻滑动时,按下事件被ViewPager消费了)
startY = (int) ev.getY(); // 重新获取起点的位置
}
endY = (int) ev.getY();
dY = endY - startY;

// 如果正在刷新,就什么都不做
if (mCurrentState == STATE_REFRESHING){
break;
}

int firstVisiblePosition = this.getFirstVisiblePosition(); // 当前显示的第一个item的位置
if (dY > 0 && firstVisiblePosition == 0){
// 下拉动作 & 当前在ListView的顶部
int padding = -mMeasuredHeight + dY;
if (padding > 0 && mCurrentState != STATE_RELEASE_TO_REFRESH){
// 切换到松开刷新状态
mCurrentState = STATE_RELEASE_TO_REFRESH;
refreshState();
}else if (padding <= 0 && mCurrentState != STATE_PULL_TO_REFRESH){
// 切换到下拉刷新状态
mCurrentState = STATE_PULL_TO_REFRESH;
refreshState();
}
// 通过修改padding来设置当前刷新控件的最新位置
mHeadView.setPadding(0,padding,0,0);
return true; // 消费此事件,处理下拉刷新控件的滑动,不需要ListView原生效果参与
}
break;
case MotionEvent.ACTION_UP:
startY = -1; // 起始坐标归0
if (mCurrentState == STATE_RELEASE_TO_REFRESH){
// 切换成正在刷新
mCurrentState = STATE_REFRESHING;
// 完整显示刷新控件
mHeadView.setPadding(0,0,0,0);
refreshState();
}else if (mCurrentState == STATE_PULL_TO_REFRESH){
// 隐藏刷新控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);
}
break;
default:
break;
}
return super.onTouchEvent(ev); // 要返回super,方便ListView原生滑动处理
}

/**
* 初始化箭头动画
*/
private void initArrowAnim(){
animUp = new RotateAnimation(0,-180, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animUp.setDuration(300);
animUp.setFillAfter(true); // 保持住动画结束的状态

animDown = new RotateAnimation(-180,0, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animDown.setDuration(300);
animDown.setFillAfter(true); // 保持住动画结束的状态
}

/**
* 根据当前状态来刷新界面
*/
private void refreshState() {
switch (mCurrentState){
case STATE_PULL_TO_REFRESH:
tvState.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
ivArrow.setAnimation(animDown);
break;
case STATE_RELEASE_TO_REFRESH:
tvState.setText("松开刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
ivArrow.setAnimation(animUp);
break;
case STATE_REFRESHING:
tvState.setText("正在刷新");
pbLoading.setVisibility(View.VISIBLE);
ivArrow.clearAnimation(); // 清理动画之后才能隐藏
ivArrow.setVisibility(View.INVISIBLE);
// 回调下拉刷新
if (mListener != null){
mListener.onRefresh();
}
break;
default:
break;
}
}

/**
* 刷新结束,隐藏控件
*/
public void onRefreshComplete(){
// 隐藏控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);

// 所有状态初始化
tvState.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
mCurrentState = STATE_PULL_TO_REFRESH;

// 更新刷新时间
setRefreshTime();
}

public void setOnRefreshListener(OnRefreshListener listener){
mListener = listener;
}

/**
* 滑动状态发生变化
* @param view
* @param scrollState
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE){
// 空闲状态
int lastPosition = getLastVisiblePosition(); // 当前显示item的最后一个位置
if (lastPosition == getCount() - 1){
// 显示加载中布局
mFootView.setPadding(0,0,0,0);
setSelection(getCount() - 1); // 显示在最后一个item的位置(展示加载中布局)

// 加载更多数据
if (mListener != null){
mListener.onLoadMore();
}
}
}
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

}

// 回调接口,通知刷新状态
public interface OnRefreshListener{
// 下拉刷新新的回调
public void onRefresh();

// 加载更多数据的回调
public void onLoadMore();
}

// 设置刷新时间
private void setRefreshTime(){
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = format.format(new Date());
tvTime.setText(time);
}
}

  1. 修改TabDetailPaper,在监听器中实现刚刚定义好的方法,代码如下:

package com.example.zhbj.base.impl;

import android.app.Activity;
import android.graphics.Color;
import android.support.annotation.NonNull;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.zhbj.R;
import com.example.zhbj.base.BaseMenuDetailPaper;
import com.example.zhbj.domain.NewsMenu;
import com.example.zhbj.domain.NewsTab;
import com.example.zhbj.global.GlobalConstans;
import com.example.zhbj.util.CacheUtil;
import com.example.zhbj.view.RefreshListView;
import com.example.zhbj.view.TopNewsViewPaper;
import com.google.gson.Gson;
import com.lidroid.xutils.BitmapUtils;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest;
import com.lidroid.xutils.view.annotation.ViewInject;
import com.viewpagerindicator.CirclePageIndicator;

import java.util.ArrayList;

/**
* 页签详情页,包含北京、中国、国际等页签
* ViewPagerIndicator
* 1.引入ViewPagerIndicator库
* 2.解决v4冲突,用大的版本覆盖小的版本
* 3.仿照sample中的程序进行拷贝SampleTabsDefault
*/
public class TabDetailPaper extends BaseMenuDetailPaper {

/**
* 当前页签数据
*/
private NewsMenu.NewsTabData newsTabData;

/**
* 文本框对象
*/
// private TextView view;

/**
* ViewPager对象
*/
@ViewInject(R.id.vp_tab_detail)
private TopNewsViewPaper mViewPaper;

/**
* 请求的URL地址
*/
private String mUrl;

/**
* 保存头条新闻图片的集合
*/
private ArrayList<NewsTab.TopNews> mTopNewsList;

/**
* TextView控件实例
*/
@ViewInject(R.id.tv_title2)
private TextView tvTitle;

/**
* Indicator控件实例
*/
@ViewInject(R.id.indicator2)
private CirclePageIndicator mIndicator;

/**
* ListView控件实例
*/
@ViewInject(R.id.lv_list)
private RefreshListView lvList;

/**
* 存储新闻列表的集合
*/
private ArrayList<NewsTab.News> mNewsList;

public TabDetailPaper(Activity activity, NewsMenu.NewsTabData newsTabData) {
super(activity);
this.newsTabData = newsTabData;
mUrl = GlobalConstans.SERVER_URL + newsTabData.url;
}

@Override
public View initViews() {
/*
view = new TextView(mActivity);
view.setTextSize(22);
view.setTextColor(Color.RED);
view.setGravity(Gravity.CENTER); // 居中显示
view.setText("页签");
*/
View view = View.inflate(mActivity,R.layout.paper_tab_detail,null);

// 加载头条新闻的头布局
View headerView = View.inflate(mActivity,R.layout.list_item_header,null);
ViewUtils.inject(this,view);
ViewUtils.inject(this,headerView);
lvList.addHeaderView(headerView); // 给ListView添加头布局

// 设置下拉刷新的监听
lvList.setOnRefreshListener(new RefreshListView.OnRefreshListener() {
@Override
public void onRefresh() {
// 刷新数据
getDataFromServer();
}

@Override
public void onLoadMore() {

}
});
return view;
}

@Override
public void initData() {
// view.setText(newsTabData.title); // 修改当前布局的数据
String cache = CacheUtil.getCache(mActivity, mUrl);
if (!TextUtils.isEmpty(cache)){
// 有缓存
processData(cache);
}
getDataFromServer();
}

/**
* 从服务器中获取数据
*/
private void getDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpRequest.HttpMethod.GET, mUrl, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
processData(result);
CacheUtil.setCache(mActivity,mUrl,result);

// 隐藏下拉刷新控件
lvList.onRefreshComplete();
}

@Override
public void onFailure(HttpException e, String s) {
e.printStackTrace();
Toast.makeText(mActivity,s,Toast.LENGTH_SHORT).show();

// 隐藏下拉刷新控件
lvList.onRefreshComplete();
}
});
}

private void processData(String result) {
Gson gson = new Gson();
NewsTab newsTab = gson.fromJson(result, NewsTab.class);

// 初始化头条新闻数据
mTopNewsList = newsTab.data.topnews;
if (mTopNewsList != null){
mViewPaper.setAdapter(new TopNewsAdapter());
mIndicator.setViewPager(mViewPaper); // 将圆形指示器和viewpager绑定
mIndicator.setSnap(true); // 快照展示方式
mIndicator.onPageSelected(0); // 将圆点位置归零,保证圆点和页面同步
mIndicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i1) {

}

@Override
public void onPageSelected(int i) {
// 更新头条新闻的标题
tvTitle.setText(mTopNewsList.get(i).title);
}

@Override
public void onPageScrollStateChanged(int i) {

}
});

// 初始化首页标题
tvTitle.setText(mTopNewsList.get(0).title);
}

/**
* 初始化新闻列表数据
*/
mNewsList = newsTab.data.news;
if (mNewsList != null){
lvList.setAdapter(new NewsAdapter());
}
}

class TopNewsAdapter extends PagerAdapter{

/**
* xUtils中的BitmapUtils工具类
*/
private BitmapUtils mBitmapUtils;

public TopNewsAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
// 设置加载中的默认图片
mBitmapUtils.configDefaultLoadingImage(R.drawable.pic_item_list_default);
}

@Override
public int getCount() {
return mTopNewsList.size();
}

@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
return view == o;
}

@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
ImageView view = new ImageView(mActivity);
NewsTab.TopNews topNews = mTopNewsList.get(position);
String topimage = topNews.topimage; // 图片的下载链接

view.setScaleType(ImageView.ScaleType.FIT_XY); // 设置缩放模式:宽高匹配窗体
/**
* 1,根据url下载图片
* 2.将图片设置给ImageView
* 3.将图片作成缓存
* 4.避免内存溢出
* 由于工程量巨大,这里使用xUtils中的BitmapUtils里的api来完成这四个逻辑
*/
mBitmapUtils.display(view,topimage);

container.addView(view);
return view;
}

@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object);
}
}

/**
* 新闻列表适配器
*/
class NewsAdapter extends BaseAdapter{

/**
* BitmapUtils工具类
*/
private BitmapUtils mBitmapUtils;

public NewsAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
mBitmapUtils.configDefaultLoadingImage(R.drawable.news_pic_default);
}

@Override
public int getCount() {
return mNewsList.size();
}

@Override
public NewsTab.News getItem(int position) {
return mNewsList.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null){
convertView = View.inflate(mActivity,R.layout.list_item_news,null);
holder = new ViewHolder();
holder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_icon);
holder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title3);
holder.tvTime = (TextView) convertView.findViewById(R.id.tv_time);

convertView.setTag(holder);
}else {
holder = (ViewHolder) convertView.getTag();
}
NewsTab.News info = getItem(position);
holder.tvTitle.setText(info.title);
holder.tvTime.setText(info.pubdate);
mBitmapUtils.display(holder.ivIcon,info.listimage);
return convertView;
}
}

static class ViewHolder{
public ImageView ivIcon;
public TextView tvTitle;
public TextView tvTime;
}

}

  1. 修改RefreshListView,添加一个标志位,保证只装载一次数据,并且完善相应逻辑,代码如下:

package com.example.zhbj.view;

import android.content.Context;
import android.media.Image;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.zhbj.R;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
* 下拉刷新ListView
*/
public class RefreshListView extends ListView implements AbsListView.OnScrollListener {

/**
* 头布局的高度
*/
private int mMeasuredHeight;

/**
* 头布局对象
*/
private View mHeadView;

/**
* 脚布局的高度
*/
private int mMeasuredFootHeight;

/**
* 脚布局对象
*/
private View mFootView;

/**
* 起始的Y坐标
*/
private int startY = -1;

/**
* 结束的Y坐标
*/
private int endY;

/**
* Y坐标轴的偏移量
*/
private int dY;

/**
* 下拉刷新状态标志位
*/
private static final int STATE_PULL_TO_REFRESH = 0;

/**
* 松开刷新状态标志位
*/
private static final int STATE_RELEASE_TO_REFRESH = 1;

/**
* 正在刷新状态标志位
*/
private static final int STATE_REFRESHING = 2;

/**
* 当前状态的标志位,默认为松开刷新
*/
private int mCurrentState = STATE_PULL_TO_REFRESH;

/**
* 状态文本组件
*/
private TextView tvState;

/**
* 时间文本组件
*/
private TextView tvTime;

/**
* 箭头图标
*/
private ImageView ivArrow;

/**
* 加载条组件
*/
private ProgressBar pbLoading;

/**
* 箭头向上的动画对象
*/
private RotateAnimation animUp;

/**
* 箭头向下的动画对象
*/
private RotateAnimation animDown;

/**
* 监听器回调接口
*/
private OnRefreshListener mListener;

/**
* 标记是否正在加载更多
*/
private boolean isLoadMore = false;

public RefreshListView(Context context) {
this(context, null);
}

public RefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}

public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHeaderView();
initFooterView();
}

/**
* 初始化头布局
*/
private void initHeaderView(){
mHeadView = View.inflate(getContext(),R.layout.pull_to_refresh_header,null);
addHeaderView(mHeadView); // 给ListView添加头布局

tvState = (TextView) mHeadView.findViewById(R.id.tv_state);
tvTime = (TextView) mHeadView.findViewById(R.id.tv_time2);
ivArrow = (ImageView) mHeadView.findViewById(R.id.iv_arrow);
pbLoading = (ProgressBar) mHeadView.findViewById(R.id.pb_loading);
/*
隐藏头布局
1.获取当前头布局高度
2.设置负paddingTop,布局就会向上走
*/
// int height = mHeadView.getHeight(); 不能这样获取宽高,因为没有绘制完毕
mHeadView.measure(0,0); // 手动测量,宽高传0表示不参与具体宽高的设定,全由系统底层决定
mMeasuredHeight = mHeadView.getMeasuredHeight(); // 获取测量后的高度
mHeadView.setPadding(0,-mMeasuredHeight,0,0);

initArrowAnim();
setRefreshTime();
}

/**
* 初始化脚布局
*/
private void initFooterView(){
mFootView = View.inflate(getContext(),R.layout.pull_to_refresh_foot,null);
addFooterView(mFootView);

mFootView.measure(0,0);
mMeasuredFootHeight = mFootView.getMeasuredHeight();
// 隐藏脚布局
mFootView.setPadding(0,-mMeasuredFootHeight,0,0);

// 设置滑动监听
setOnScrollListener(this);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1){
// 没有获取到按下的事件(按住头条新闻滑动时,按下事件被ViewPager消费了)
startY = (int) ev.getY(); // 重新获取起点的位置
}
endY = (int) ev.getY();
dY = endY - startY;

// 如果正在刷新,就什么都不做
if (mCurrentState == STATE_REFRESHING){
break;
}

int firstVisiblePosition = this.getFirstVisiblePosition(); // 当前显示的第一个item的位置
if (dY > 0 && firstVisiblePosition == 0){
// 下拉动作 & 当前在ListView的顶部
int padding = -mMeasuredHeight + dY;
if (padding > 0 && mCurrentState != STATE_RELEASE_TO_REFRESH){
// 切换到松开刷新状态
mCurrentState = STATE_RELEASE_TO_REFRESH;
refreshState();
}else if (padding <= 0 && mCurrentState != STATE_PULL_TO_REFRESH){
// 切换到下拉刷新状态
mCurrentState = STATE_PULL_TO_REFRESH;
refreshState();
}
// 通过修改padding来设置当前刷新控件的最新位置
mHeadView.setPadding(0,padding,0,0);
return true; // 消费此事件,处理下拉刷新控件的滑动,不需要ListView原生效果参与
}
break;
case MotionEvent.ACTION_UP:
startY = -1; // 起始坐标归0
if (mCurrentState == STATE_RELEASE_TO_REFRESH){
// 切换成正在刷新
mCurrentState = STATE_REFRESHING;
// 完整显示刷新控件
mHeadView.setPadding(0,0,0,0);
refreshState();
}else if (mCurrentState == STATE_PULL_TO_REFRESH){
// 隐藏刷新控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);
}
break;
default:
break;
}
return super.onTouchEvent(ev); // 要返回super,方便ListView原生滑动处理
}

/**
* 初始化箭头动画
*/
private void initArrowAnim(){
animUp = new RotateAnimation(0,-180, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animUp.setDuration(300);
animUp.setFillAfter(true); // 保持住动画结束的状态

animDown = new RotateAnimation(-180,0, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animDown.setDuration(300);
animDown.setFillAfter(true); // 保持住动画结束的状态
}

/**
* 根据当前状态来刷新界面
*/
private void refreshState() {
switch (mCurrentState){
case STATE_PULL_TO_REFRESH:
tvState.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
ivArrow.setAnimation(animDown);
break;
case STATE_RELEASE_TO_REFRESH:
tvState.setText("松开刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
ivArrow.setAnimation(animUp);
break;
case STATE_REFRESHING:
tvState.setText("正在刷新");
pbLoading.setVisibility(View.VISIBLE);
ivArrow.clearAnimation(); // 清理动画之后才能隐藏
ivArrow.setVisibility(View.INVISIBLE);
// 回调下拉刷新
if (mListener != null){
mListener.onRefresh();
}
break;
default:
break;
}
}

/**
* 刷新结束,隐藏控件
*/
public void onRefreshComplete(){
// 隐藏控件
mHeadView.setPadding(0,-mMeasuredHeight,0,0);

// 所有状态初始化
tvState.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.setVisibility(View.VISIBLE);
mCurrentState = STATE_PULL_TO_REFRESH;

// 更新刷新时间
setRefreshTime();
}

public void setOnRefreshListener(OnRefreshListener listener){
mListener = listener;
}

/**
* 滑动状态发生变化
* @param view
* @param scrollState
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE){
// 空闲状态
int lastPosition = getLastVisiblePosition(); // 当前显示item的最后一个位置
if (lastPosition == getCount() - 1 && !isLoadMore){
// 初始化加载数据标志位
isLoadMore = true;

// 显示加载中布局
mFootView.setPadding(0,0,0,0);
setSelection(getCount() - 1); // 显示在最后一个item的位置(展示加载中布局)

// 加载更多数据
if (mListener != null){
mListener.onLoadMore();
}
}
}
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

}

// 回调接口,通知刷新状态
public interface OnRefreshListener{
// 下拉刷新新的回调
public void onRefresh();

// 加载更多数据的回调
public void onLoadMore();
}

// 设置刷新时间
private void setRefreshTime(){
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = format.format(new Date());
tvTime.setText(time);
}
}

  1. 修改TabDetailPaper,实现onLoadMore()方法,读取更多数据,再修改processData()方法,拼接进入到更多信息的URL,代码如下

package com.example.zhbj.base.impl;

import android.app.Activity;
import android.graphics.Color;
import android.support.annotation.NonNull;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.zhbj.R;
import com.example.zhbj.base.BaseMenuDetailPaper;
import com.example.zhbj.domain.NewsMenu;
import com.example.zhbj.domain.NewsTab;
import com.example.zhbj.global.GlobalConstans;
import com.example.zhbj.util.CacheUtil;
import com.example.zhbj.view.RefreshListView;
import com.example.zhbj.view.TopNewsViewPaper;
import com.google.gson.Gson;
import com.lidroid.xutils.BitmapUtils;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest;
import com.lidroid.xutils.view.annotation.ViewInject;
import com.viewpagerindicator.CirclePageIndicator;

import java.util.ArrayList;

/**
* 页签详情页,包含北京、中国、国际等页签
* ViewPagerIndicator
* 1.引入ViewPagerIndicator库
* 2.解决v4冲突,用大的版本覆盖小的版本
* 3.仿照sample中的程序进行拷贝SampleTabsDefault
*/
public class TabDetailPaper extends BaseMenuDetailPaper {

/**
* 当前页签数据
*/
private NewsMenu.NewsTabData newsTabData;

/**
* 文本框对象
*/
// private TextView view;

/**
* ViewPager对象
*/
@ViewInject(R.id.vp_tab_detail)
private TopNewsViewPaper mViewPaper;

/**
* 请求的URL地址
*/
private String mUrl;

/**
* 保存头条新闻图片的集合
*/
private ArrayList<NewsTab.TopNews> mTopNewsList;

/**
* TextView控件实例
*/
@ViewInject(R.id.tv_title2)
private TextView tvTitle;

/**
* Indicator控件实例
*/
@ViewInject(R.id.indicator2)
private CirclePageIndicator mIndicator;

/**
* ListView控件实例
*/
@ViewInject(R.id.lv_list)
private RefreshListView lvList;

/**
* 存储新闻列表的集合
*/
private ArrayList<NewsTab.News> mNewsList;

/**
* 加载更多信息的Url
*/
private String mMoreUrl;

public TabDetailPaper(Activity activity, NewsMenu.NewsTabData newsTabData) {
super(activity);
this.newsTabData = newsTabData;
mUrl = GlobalConstans.SERVER_URL + newsTabData.url;
}

@Override
public View initViews() {
/*
view = new TextView(mActivity);
view.setTextSize(22);
view.setTextColor(Color.RED);
view.setGravity(Gravity.CENTER); // 居中显示
view.setText("页签");
*/
View view = View.inflate(mActivity,R.layout.paper_tab_detail,null);

// 加载头条新闻的头布局
View headerView = View.inflate(mActivity,R.layout.list_item_header,null);
ViewUtils.inject(this,view);
ViewUtils.inject(this,headerView);
lvList.addHeaderView(headerView); // 给ListView添加头布局

// 设置下拉刷新的监听
lvList.setOnRefreshListener(new RefreshListView.OnRefreshListener() {
@Override
public void onRefresh() {
// 刷新数据
getDataFromServer();
}

@Override
public void onLoadMore() {
if (mMoreUrl != null){

}else {
Toast.makeText(mActivity,"没有更多数据啦",Toast.LENGTH_SHORT).show();
}
}
});
return view;
}

@Override
public void initData() {
// view.setText(newsTabData.title); // 修改当前布局的数据
String cache = CacheUtil.getCache(mActivity, mUrl);
if (!TextUtils.isEmpty(cache)){
// 有缓存
processData(cache);
}
getDataFromServer();
}

/**
* 从服务器中获取数据
*/
private void getDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpRequest.HttpMethod.GET, mUrl, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
processData(result);
CacheUtil.setCache(mActivity,mUrl,result);

// 隐藏下拉刷新控件
lvList.onRefreshComplete();
}

@Override
public void onFailure(HttpException e, String s) {
e.printStackTrace();
Toast.makeText(mActivity,s,Toast.LENGTH_SHORT).show();

// 隐藏下拉刷新控件
lvList.onRefreshComplete();
}
});
}

private void processData(String result) {
Gson gson = new Gson();
NewsTab newsTab = gson.fromJson(result, NewsTab.class);

// 获取下一页的数据地址
String more = newsTab.data.more;
if (!TextUtils.isEmpty(more)){
mMoreUrl = GlobalConstans.SERVER_URL + more;
}
else {
mMoreUrl = null;
}
// 初始化头条新闻数据
mTopNewsList = newsTab.data.topnews;
if (mTopNewsList != null){
mViewPaper.setAdapter(new TopNewsAdapter());
mIndicator.setViewPager(mViewPaper); // 将圆形指示器和viewpager绑定
mIndicator.setSnap(true); // 快照展示方式
mIndicator.onPageSelected(0); // 将圆点位置归零,保证圆点和页面同步
mIndicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i1) {

}

@Override
public void onPageSelected(int i) {
// 更新头条新闻的标题
tvTitle.setText(mTopNewsList.get(i).title);
}

@Override
public void onPageScrollStateChanged(int i) {

}
});

// 初始化首页标题
tvTitle.setText(mTopNewsList.get(0).title);
}

/**
* 初始化新闻列表数据
*/
mNewsList = newsTab.data.news;
if (mNewsList != null){
lvList.setAdapter(new NewsAdapter());
}
}

class TopNewsAdapter extends PagerAdapter{

/**
* xUtils中的BitmapUtils工具类
*/
private BitmapUtils mBitmapUtils;

public TopNewsAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
// 设置加载中的默认图片
mBitmapUtils.configDefaultLoadingImage(R.drawable.pic_item_list_default);
}

@Override
public int getCount() {
return mTopNewsList.size();
}

@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
return view == o;
}

@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
ImageView view = new ImageView(mActivity);
NewsTab.TopNews topNews = mTopNewsList.get(position);
String topimage = topNews.topimage; // 图片的下载链接

view.setScaleType(ImageView.ScaleType.FIT_XY); // 设置缩放模式:宽高匹配窗体
/**
* 1,根据url下载图片
* 2.将图片设置给ImageView
* 3.将图片作成缓存
* 4.避免内存溢出
* 由于工程量巨大,这里使用xUtils中的BitmapUtils里的api来完成这四个逻辑
*/
mBitmapUtils.display(view,topimage);

container.addView(view);
return view;
}

@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object);
}
}

/**
* 新闻列表适配器
*/
class NewsAdapter extends BaseAdapter{

/**
* BitmapUtils工具类
*/
private BitmapUtils mBitmapUtils;

public NewsAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
mBitmapUtils.configDefaultLoadingImage(R.drawable.news_pic_default);
}

@Override
public int getCount() {
return mNewsList.size();
}

@Override
public NewsTab.News getItem(int position) {
return mNewsList.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null){
convertView = View.inflate(mActivity,R.layout.list_item_news,null);
holder = new ViewHolder();
holder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_icon);
holder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title3);
holder.tvTime = (TextView) convertView.findViewById(R.id.tv_time);

convertView.setTag(holder);
}else {
holder = (ViewHolder) convertView.getTag();
}
NewsTab.News info = getItem(position);
holder.tvTitle.setText(info.title);
holder.tvTime.setText(info.pubdate);
mBitmapUtils.display(holder.ivIcon,info.listimage);
return convertView;
}
}

static class ViewHolder{
public ImageView ivIcon;
public TextView tvTitle;
public TextView tvTime;
}

}

  1. 修改TabDetailPaper,增加getMoreDataFromServer()方法,读取更多数据,类似于getDataFromServer()方法,只是url的地址不相同,代码如下:

package com.example.zhbj.base.impl;

import android.app.Activity;
import android.graphics.Color;
import android.support.annotation.NonNull;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.zhbj.R;
import com.example.zhbj.base.BaseMenuDetailPaper;
import com.example.zhbj.domain.NewsMenu;
import com.example.zhbj.domain.NewsTab;
import com.example.zhbj.global.GlobalConstans;
import com.example.zhbj.util.CacheUtil;
import com.example.zhbj.view.RefreshListView;
import com.example.zhbj.view.TopNewsViewPaper;
import com.google.gson.Gson;
import com.lidroid.xutils.BitmapUtils;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest;
import com.lidroid.xutils.view.annotation.ViewInject;
import com.viewpagerindicator.CirclePageIndicator;

import java.util.ArrayList;

/**
* 页签详情页,包含北京、中国、国际等页签
* ViewPagerIndicator
* 1.引入ViewPagerIndicator库
* 2.解决v4冲突,用大的版本覆盖小的版本
* 3.仿照sample中的程序进行拷贝SampleTabsDefault
*/
public class TabDetailPaper extends BaseMenuDetailPaper {

/**
* 当前页签数据
*/
private NewsMenu.NewsTabData newsTabData;

/**
* 文本框对象
*/
// private TextView view;

/**
* ViewPager对象
*/
@ViewInject(R.id.vp_tab_detail)
private TopNewsViewPaper mViewPaper;

/**
* 请求的URL地址
*/
private String mUrl;

/**
* 保存头条新闻图片的集合
*/
private ArrayList<NewsTab.TopNews> mTopNewsList;

/**
* TextView控件实例
*/
@ViewInject(R.id.tv_title2)
private TextView tvTitle;

/**
* Indicator控件实例
*/
@ViewInject(R.id.indicator2)
private CirclePageIndicator mIndicator;

/**
* ListView控件实例
*/
@ViewInject(R.id.lv_list)
private RefreshListView lvList;

/**
* 存储新闻列表的集合
*/
private ArrayList<NewsTab.News> mNewsList;

/**
* 加载更多信息的Url
*/
private String mMoreUrl;

public TabDetailPaper(Activity activity, NewsMenu.NewsTabData newsTabData) {
super(activity);
this.newsTabData = newsTabData;
mUrl = GlobalConstans.SERVER_URL + newsTabData.url;
}

@Override
public View initViews() {
/*
view = new TextView(mActivity);
view.setTextSize(22);
view.setTextColor(Color.RED);
view.setGravity(Gravity.CENTER); // 居中显示
view.setText("页签");
*/
View view = View.inflate(mActivity,R.layout.paper_tab_detail,null);

// 加载头条新闻的头布局
View headerView = View.inflate(mActivity,R.layout.list_item_header,null);
ViewUtils.inject(this,view);
ViewUtils.inject(this,headerView);
lvList.addHeaderView(headerView); // 给ListView添加头布局

// 设置下拉刷新的监听
lvList.setOnRefreshListener(new RefreshListView.OnRefreshListener() {
@Override
public void onRefresh() {
// 刷新数据
getDataFromServer();
}

@Override
public void onLoadMore() {
if (mMoreUrl != null){
getMoreDataFromServer();
}else {
Toast.makeText(mActivity,"没有更多数据啦",Toast.LENGTH_SHORT).show();
}
}
});
return view;
}

@Override
public void initData() {
// view.setText(newsTabData.title); // 修改当前布局的数据
String cache = CacheUtil.getCache(mActivity, mUrl);
if (!TextUtils.isEmpty(cache)){
// 有缓存
processData(cache);
}
getDataFromServer();
}

/**
* 从服务器中获取数据
*/
private void getDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpRequest.HttpMethod.GET, mUrl, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
processData(result);
CacheUtil.setCache(mActivity,mUrl,result);

// 隐藏下拉刷新控件
lvList.onRefreshComplete();
}

@Override
public void onFailure(HttpException e, String s) {
e.printStackTrace();
Toast.makeText(mActivity,s,Toast.LENGTH_SHORT).show();

// 隐藏下拉刷新控件
lvList.onRefreshComplete();
}
});
}

/**
* 从服务器中获取下一页数据
*/
private void getMoreDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpRequest.HttpMethod.GET, mMoreUrl, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
processData(result);

// 隐藏下拉刷新控件
lvList.onRefreshComplete();
}

@Override
public void onFailure(HttpException e, String s) {
e.printStackTrace();
Toast.makeText(mActivity,s,Toast.LENGTH_SHORT).show();

// 隐藏下拉刷新控件
lvList.onRefreshComplete();
}
});
}

private void processData(String result) {
Gson gson = new Gson();
NewsTab newsTab = gson.fromJson(result, NewsTab.class);

// 获取下一页的数据地址
String more = newsTab.data.more;
if (!TextUtils.isEmpty(more)){
mMoreUrl = GlobalConstans.SERVER_URL + more;
}
else {
mMoreUrl = null;
}
// 初始化头条新闻数据
mTopNewsList = newsTab.data.topnews;
if (mTopNewsList != null){
mViewPaper.setAdapter(new TopNewsAdapter());
mIndicator.setViewPager(mViewPaper); // 将圆形指示器和viewpager绑定
mIndicator.setSnap(true); // 快照展示方式
mIndicator.onPageSelected(0); // 将圆点位置归零,保证圆点和页面同步
mIndicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i1) {

}

@Override
public void onPageSelected(int i) {
// 更新头条新闻的标题
tvTitle.setText(mTopNewsList.get(i).title);
}

@Override
public void onPageScrollStateChanged(int i) {

}
});

// 初始化首页标题
tvTitle.setText(mTopNewsList.get(0).title);
}

/**
* 初始化新闻列表数据
*/
mNewsList = newsTab.data.news;
if (mNewsList != null){
lvList.setAdapter(new NewsAdapter());
}
}

class TopNewsAdapter extends PagerAdapter{

/**
* xUtils中的BitmapUtils工具类
*/
private BitmapUtils mBitmapUtils;

public TopNewsAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
// 设置加载中的默认图片
mBitmapUtils.configDefaultLoadingImage(R.drawable.pic_item_list_default);
}

@Override
public int getCount() {
return mTopNewsList.size();
}

@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
return view == o;
}

@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
ImageView view = new ImageView(mActivity);
NewsTab.TopNews topNews = mTopNewsList.get(position);
String topimage = topNews.topimage; // 图片的下载链接

view.setScaleType(ImageView.ScaleType.FIT_XY); // 设置缩放模式:宽高匹配窗体
/**
* 1,根据url下载图片
* 2.将图片设置给ImageView
* 3.将图片作成缓存
* 4.避免内存溢出
* 由于工程量巨大,这里使用xUtils中的BitmapUtils里的api来完成这四个逻辑
*/
mBitmapUtils.display(view,topimage);

container.addView(view);
return view;
}

@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object);
}
}

/**
* 新闻列表适配器
*/
class NewsAdapter extends BaseAdapter{

/**
* BitmapUtils工具类
*/
private BitmapUtils mBitmapUtils;

public NewsAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
mBitmapUtils.configDefaultLoadingImage(R.drawable.news_pic_default);
}

@Override
public int getCount() {
return mNewsList.size();
}

@Override
public NewsTab.News getItem(int position) {
return mNewsList.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null){
convertView = View.inflate(mActivity,R.layout.list_item_news,null);
holder = new ViewHolder();
holder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_icon);
holder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title3);
holder.tvTime = (TextView) convertView.findViewById(R.id.tv_time);

convertView.setTag(holder);
}else {
holder = (ViewHolder) convertView.getTag();
}
NewsTab.News info = getItem(position);
holder.tvTitle.setText(info.title);
holder.tvTime.setText(info.pubdate);
mBitmapUtils.display(holder.ivIcon,info.listimage);
return convertView;
}
}

static class ViewHolder{
public ImageView ivIcon;
public TextView tvTitle;
public TextView tvTime;
}

}

  1. 修改TabDetailPaper,修改processData()方法,传入布尔类型的参数,表示是否加载更多数据,并进行相应修改,代码如下:

package com.example.zhbj.base.impl;

import android.app.Activity;
import android.graphics.Color;
import android.support.annotation.NonNull;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.zhbj.R;
import com.example.zhbj.base.BaseMenuDetailPaper;
import com.example.zhbj.domain.NewsMenu;
import com.example.zhbj.domain.NewsTab;
import com.example.zhbj.global.GlobalConstans;
import com.example.zhbj.util.CacheUtil;
import com.example.zhbj.view.RefreshListView;
import com.example.zhbj.view.TopNewsViewPaper;
import com.google.gson.Gson;
import com.lidroid.xutils.BitmapUtils;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest;
import com.lidroid.xutils.view.annotation.ViewInject;
import com.viewpagerindicator.CirclePageIndicator;

import java.util.ArrayList;

/**
* 页签详情页,包含北京、中国、国际等页签
* ViewPagerIndicator
* 1.引入ViewPagerIndicator库
* 2.解决v4冲突,用大的版本覆盖小的版本
* 3.仿照sample中的程序进行拷贝SampleTabsDefault
*/
public class TabDetailPaper extends BaseMenuDetailPaper {

/**
* 当前页签数据
*/
private NewsMenu.NewsTabData newsTabData;

/**
* 文本框对象
*/
// private TextView view;

/**
* ViewPager对象
*/
@ViewInject(R.id.vp_tab_detail)
private TopNewsViewPaper mViewPaper;

/**
* 请求的URL地址
*/
private String mUrl;

/**
* 保存头条新闻图片的集合
*/
private ArrayList<NewsTab.TopNews> mTopNewsList;

/**
* TextView控件实例
*/
@ViewInject(R.id.tv_title2)
private TextView tvTitle;

/**
* Indicator控件实例
*/
@ViewInject(R.id.indicator2)
private CirclePageIndicator mIndicator;

/**
* ListView控件实例
*/
@ViewInject(R.id.lv_list)
private RefreshListView lvList;

/**
* 存储新闻列表的集合
*/
private ArrayList<NewsTab.News> mNewsList;

/**
* 加载更多信息的Url
*/
private String mMoreUrl;

/**
* 新闻详情的适配器
*/
private NewsAdapter mNewsAdapter;

public TabDetailPaper(Activity activity, NewsMenu.NewsTabData newsTabData) {
super(activity);
this.newsTabData = newsTabData;
mUrl = GlobalConstans.SERVER_URL + newsTabData.url;
}

@Override
public View initViews() {
/*
view = new TextView(mActivity);
view.setTextSize(22);
view.setTextColor(Color.RED);
view.setGravity(Gravity.CENTER); // 居中显示
view.setText("页签");
*/
View view = View.inflate(mActivity,R.layout.paper_tab_detail,null);

// 加载头条新闻的头布局
View headerView = View.inflate(mActivity,R.layout.list_item_header,null);
ViewUtils.inject(this,view);
ViewUtils.inject(this,headerView);
lvList.addHeaderView(headerView); // 给ListView添加头布局

// 设置下拉刷新的监听
lvList.setOnRefreshListener(new RefreshListView.OnRefreshListener() {
@Override
public void onRefresh() {
// 刷新数据
getDataFromServer();
}

@Override
public void onLoadMore() {
if (mMoreUrl != null){
getMoreDataFromServer();
}else {
Toast.makeText(mActivity,"没有更多数据啦",Toast.LENGTH_SHORT).show();
lvList.onRefreshComplete();
}
}
});
return view;
}

@Override
public void initData() {
// view.setText(newsTabData.title); // 修改当前布局的数据
String cache = CacheUtil.getCache(mActivity, mUrl);
if (!TextUtils.isEmpty(cache)){
// 有缓存
processData(cache,false);
}
getDataFromServer();
}

/**
* 从服务器中获取数据
*/
private void getDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpRequest.HttpMethod.GET, mUrl, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
processData(result,false);
CacheUtil.setCache(mActivity,mUrl,result);

// 隐藏下拉刷新控件
lvList.onRefreshComplete();
}

@Override
public void onFailure(HttpException e, String s) {
e.printStackTrace();
Toast.makeText(mActivity,s,Toast.LENGTH_SHORT).show();

// 隐藏下拉刷新控件
lvList.onRefreshComplete();
}
});
}

/**
* 从服务器中获取下一页数据
*/
private void getMoreDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpRequest.HttpMethod.GET, mMoreUrl, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
processData(result,true);

// 隐藏下拉刷新控件
lvList.onRefreshComplete();
}

@Override
public void onFailure(HttpException e, String s) {
e.printStackTrace();
Toast.makeText(mActivity,s,Toast.LENGTH_SHORT).show();

// 隐藏下拉刷新控件
lvList.onRefreshComplete();
}
});
}

private void processData(String result,boolean isMore) {
Gson gson = new Gson();
NewsTab newsTab = gson.fromJson(result, NewsTab.class);

// 获取下一页的数据地址
String more = newsTab.data.more;
if (!TextUtils.isEmpty(more)){
mMoreUrl = GlobalConstans.SERVER_URL + more;
}
else {
mMoreUrl = null;
}

if (!isMore){
// 初始化头条新闻数据
mTopNewsList = newsTab.data.topnews;
if (mTopNewsList != null){
mViewPaper.setAdapter(new TopNewsAdapter());
mIndicator.setViewPager(mViewPaper); // 将圆形指示器和viewpager绑定
mIndicator.setSnap(true); // 快照展示方式
mIndicator.onPageSelected(0); // 将圆点位置归零,保证圆点和页面同步
mIndicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i1) {

}

@Override
public void onPageSelected(int i) {
// 更新头条新闻的标题
tvTitle.setText(mTopNewsList.get(i).title);
}

@Override
public void onPageScrollStateChanged(int i) {

}
});

// 初始化首页标题
tvTitle.setText(mTopNewsList.get(0).title);
}

/**
* 初始化新闻列表数据
*/
mNewsList = newsTab.data.news;
if (mNewsList != null){
mNewsAdapter = new NewsAdapter();
lvList.setAdapter(new NewsAdapter());
}
}else {
// 加载更多
ArrayList<NewsTab.News> moreNews = newsTab.data.news;
mNewsList.addAll(moreNews); // 追加更多数据
// 刷新listview
mNewsAdapter.notifyDataSetChanged();
}

}

class TopNewsAdapter extends PagerAdapter{

/**
* xUtils中的BitmapUtils工具类
*/
private BitmapUtils mBitmapUtils;

public TopNewsAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
// 设置加载中的默认图片
mBitmapUtils.configDefaultLoadingImage(R.drawable.pic_item_list_default);
}

@Override
public int getCount() {
return mTopNewsList.size();
}

@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
return view == o;
}

@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
ImageView view = new ImageView(mActivity);
NewsTab.TopNews topNews = mTopNewsList.get(position);
String topimage = topNews.topimage; // 图片的下载链接

view.setScaleType(ImageView.ScaleType.FIT_XY); // 设置缩放模式:宽高匹配窗体
/**
* 1,根据url下载图片
* 2.将图片设置给ImageView
* 3.将图片作成缓存
* 4.避免内存溢出
* 由于工程量巨大,这里使用xUtils中的BitmapUtils里的api来完成这四个逻辑
*/
mBitmapUtils.display(view,topimage);

container.addView(view);
return view;
}

@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object);
}
}

/**
* 新闻列表适配器
*/
class NewsAdapter extends BaseAdapter{

/**
* BitmapUtils工具类
*/
private BitmapUtils mBitmapUtils;

public NewsAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
mBitmapUtils.configDefaultLoadingImage(R.drawable.news_pic_default);
}

@Override
public int getCount() {
return mNewsList.size();
}

@Override
public NewsTab.News getItem(int position) {
return mNewsList.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null){
convertView = View.inflate(mActivity,R.layout.list_item_news,null);
holder = new ViewHolder();
holder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_icon);
holder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title3);
holder.tvTime = (TextView) convertView.findViewById(R.id.tv_time);

convertView.setTag(holder);
}else {
holder = (ViewHolder) convertView.getTag();
}
NewsTab.News info = getItem(position);
holder.tvTitle.setText(info.title);
holder.tvTime.setText(info.pubdate);
mBitmapUtils.display(holder.ivIcon,info.listimage);
return convertView;
}
}

static class ViewHolder{
public ImageView ivIcon;
public TextView tvTitle;
public TextView tvTime;
}

}

10.本地记录已读未读状态

下拉刷新和分页刷新的效果已经实现了,现在需要记录是否点击过任意一条新闻的状态,即点击过就变成灰色,未点击则变成黑色

修改TabDetailPaper,修改initViews()方法,为lvList注册点击事件监听器,代码如下:

// 设置点击事件
lvList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 头布局也算位置,所以使用position时要将头布局个数减掉
int headerViewsCount = lvList.getHeaderViewsCount();
position -= headerViewsCount;
NewsTab.News news = mNewsList.get(position);

// 标记已读未读:将已读新闻id保存在sp中
// key: "read_ids"
// value: 11000,11001,11002...
// 读取现有的id
String readIds = PrefUtils.getString(mActivity, "read_ids", "");
if (!readIds.contains(news.id)){
// 在现有id基础上追加新的id
readIds = readIds + news.id + ",";
// 保存最新的id集合
PrefUtils.putString(mActivity,"read_ids",readIds);
}

}
});

11.根据已读未读状态刷新页面

现在我们能获取到是否点击新闻,接下来就是要根据这个状态来刷新页面

  1. 修改TabDetailPaper,修改getView()方法,用于刷新页面,代码如下:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null){
convertView = View.inflate(mActivity,R.layout.list_item_news,null);
holder = new ViewHolder();
holder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_icon);
holder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title3);
holder.tvTime = (TextView) convertView.findViewById(R.id.tv_time);

convertView.setTag(holder);
}else {
holder = (ViewHolder) convertView.getTag();
}
NewsTab.News info = getItem(position);
holder.tvTitle.setText(info.title);
holder.tvTime.setText(info.pubdate);
mBitmapUtils.display(holder.ivIcon,info.listimage);

// 判断已读未读
String readIds = PrefUtils.getString(mActivity, "read_ids", "");
if (readIds.contains(info.id)){
holder.tvTitle.setTextColor(Color.GRAY);
}
else {
holder.tvTitle.setTextColor(Color.BLACK);
}
return convertView;
}
}

  1. 修改TabDetailPaper,修改lvList的点击事件监听器,用于刷新lvList,代码如下:

// 设置点击事件
lvList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 头布局也算位置,所以使用position时要将头布局个数减掉
int headerViewsCount = lvList.getHeaderViewsCount();
position -= headerViewsCount;
NewsTab.News news = mNewsList.get(position);

// 标记已读未读:将已读新闻id保存在sp中
// key: "read_ids"
// value: 11000,11001,11002...
// 读取现有的id
String readIds = PrefUtils.getString(mActivity, "read_ids", "");
if (!readIds.contains(news.id)){
// 在现有id基础上追加新的id
readIds = readIds + news.id + ",";
// 保存最新的id集合
PrefUtils.putString(mActivity,"read_ids",readIds);
}
// 刷新ListView,全局刷新
// mNewsAdapter.notifyDataSetChanged();
// 刷新ListView,局部刷新
TextView tvTitle = (TextView) view.findViewById(R.id.tv_title3);
tvTitle.setTextColor(Color.GRAY);
}
});

12.新闻详情页布局开发

我们完成了列表页面的逻辑,现在需要实现点击某条新闻后进入新闻详情的页面

  1. 新建一个活动NewsDetailActivity,代表点击某条新闻后可以查看详情的页面
  2. 修改TabDetailPaper,修改lvList的点击事件,添加跳入NewsDetailActivity活动的逻辑,代码如下:

// 设置点击事件
lvList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 头布局也算位置,所以使用position时要将头布局个数减掉
int headerViewsCount = lvList.getHeaderViewsCount();
position -= headerViewsCount;
NewsTab.News news = mNewsList.get(position);

// 标记已读未读:将已读新闻id保存在sp中
// key: "read_ids"
// value: 11000,11001,11002...
// 读取现有的id
String readIds = PrefUtils.getString(mActivity, "read_ids", "");
if (!readIds.contains(news.id)){
// 在现有id基础上追加新的id
readIds = readIds + news.id + ",";
// 保存最新的id集合
PrefUtils.putString(mActivity,"read_ids",readIds);
}
// 刷新ListView,全局刷新
// mNewsAdapter.notifyDataSetChanged();
// 刷新ListView,局部刷新
TextView tvTitle = (TextView) view.findViewById(R.id.tv_title3);
tvTitle.setTextColor(Color.GRAY);

// 跳到新闻详情页
Intent intent = new Intent(mActivity, NewsDetailActivity.class);
mActivity.startActivity(intent);
}
});

  1. 修改title_bar.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:background="@drawable/title_red_bg">

<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:textColor="#fff"
android:textSize="25sp"
android:text=""/>

<ImageButton
android:id="@+id/btn_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@null"
android:layout_marginLeft="10dp"
android:src="@drawable/img_menu"/>

<ImageButton
android:id="@+id/btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@null"
android:layout_marginLeft="10dp"
android:visibility="gone"
android:src="@drawable/back"/>

<LinearLayout
android:id="@+id/ll_control"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:visibility="gone"
android:orientation="horizontal">
<ImageButton
android:id="@+id/btn_textsize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:layout_marginRight="10dp"
android:src="@drawable/icon_textsize"/>

<ImageButton
android:id="@+id/btn_share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:src="@drawable/icon_share"/>
</LinearLayout>

</RelativeLayout>

  1. 修改activity_newss_detail.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="match_parent"
android:orientation="vertical">

<include layout="@layout/title_bar"/>

</LinearLayout>

  1. 修改NewsDetailActivity,获取控件实例,并且添加initView()方法,用于初始化控件,代码如下:

package com.example.zhbj;

import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.ImageButton;
import android.widget.LinearLayout;

import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.view.annotation.ViewInject;

/**
* 新闻详情页
*/
public class NewsDetailActivity extends Activity {

/**
* 返回按钮
*/
@ViewInject(R.id.btn_back)
private ImageButton btnBack;

/**
* 菜单按钮
*/
@ViewInject(R.id.btn_menu)
private ImageButton btnMenu;

/**
* 包含两个功能按钮的线性布局
*/
@ViewInject(R.id.ll_control)
private LinearLayout llControl;

/**
* 字体大小按钮
*/
@ViewInject(R.id.btn_textsize)
private ImageButton btnTextSize;

/**
* 分享按钮
*/
@ViewInject(R.id.btn_share)
private ImageButton btnShare;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_news_detail);
ViewUtils.inject(this);

initViews();
}

/**
* 初始化布局
*/
private void initViews() {
btnBack.setVisibility(View.VISIBLE);
btnMenu.setVisibility(View.GONE);
llControl.setVisibility(View.VISIBLE);
}
}

13.加载新闻网页 & 显示进度条

为了显示排版复杂的新闻内容,就需要使用一个高级的组件——WebView,以此来直接加载好网页规范的排版

  1. 修改activity_news_detail.xml,添加WebView组件,代码如下:

<?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">

<include layout="@layout/title_bar"/>

<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

  1. 修改TabDetailPaper,修改lvList的点击事件,将url地址传给NewsDetailActivity,代码如下:

// 设置点击事件
lvList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 头布局也算位置,所以使用position时要将头布局个数减掉
int headerViewsCount = lvList.getHeaderViewsCount();
position -= headerViewsCount;
NewsTab.News news = mNewsList.get(position);

// 标记已读未读:将已读新闻id保存在sp中
// key: "read_ids"
// value: 11000,11001,11002...
// 读取现有的id
String readIds = PrefUtils.getString(mActivity, "read_ids", "");
if (!readIds.contains(news.id)){
// 在现有id基础上追加新的id
readIds = readIds + news.id + ",";
// 保存最新的id集合
PrefUtils.putString(mActivity,"read_ids",readIds);
}
// 刷新ListView,全局刷新
// mNewsAdapter.notifyDataSetChanged();
// 刷新ListView,局部刷新
TextView tvTitle = (TextView) view.findViewById(R.id.tv_title3);
tvTitle.setTextColor(Color.GRAY);

// 跳到新闻详情页
Intent intent = new Intent(mActivity, NewsDetailActivity.class);
intent.putExtra("url",news.url); // 传递网页
mActivity.startActivity(intent);
}
});

  1. 修改NewsDetailActivity,获取WebView的实例,并且让其加载页面,代码如下:

package com.example.zhbj;

import android.app.Activity;
import android.graphics.Bitmap;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ImageButton;
import android.widget.LinearLayout;

import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.view.annotation.ViewInject;

/**
* 新闻详情页
*/
public class NewsDetailActivity extends Activity {

/**
* 返回按钮
*/
@ViewInject(R.id.btn_back)
private ImageButton btnBack;

/**
* 菜单按钮
*/
@ViewInject(R.id.btn_menu)
private ImageButton btnMenu;

/**
* 包含两个功能按钮的线性布局
*/
@ViewInject(R.id.ll_control)
private LinearLayout llControl;

/**
* 字体大小按钮
*/
@ViewInject(R.id.btn_textsize)
private ImageButton btnTextSize;

/**
* 分享按钮
*/
@ViewInject(R.id.btn_share)
private ImageButton btnShare;

@ViewInject(R.id.webview)
private WebView mWebView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_news_detail);
ViewUtils.inject(this);
initViews();

// 获取WebView的设置对象
WebSettings settings = mWebView.getSettings();
settings.setJavaScriptEnabled(true); // 启用js功能
settings.setBuiltInZoomControls(true); // 启用显示放大/缩小的按钮,不支持已经适配好移动端的页面
settings.setUseWideViewPort(true); // 启用双击缩放
// 给WebView设置一个监听
mWebView.setWebViewClient(new WebViewClient(){
/**
* 页面开始加载
* @param view
* @param url
* @param favicon
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}

/**
* 跳转链接
* @param view
* @param url
* @return
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 强制所有跳转链接都在WebView执行
mWebView.loadUrl(url);
return true;
}

/**
* 加载结束
* @param view
* @param url
*/
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
}
});
mWebView.setWebChromeClient(new WebChromeClient(){

/**
* 获取网页标题
* @param view
* @param title
*/
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
}

/**
* 进度发生变化
* @param view
* @param newProgress
*/
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
}
});

String url = getIntent().getStringExtra("url");

// 开始加载网页
mWebView.loadUrl(url);
}

/**
* 初始化布局
*/
private void initViews() {
btnBack.setVisibility(View.VISIBLE);
btnMenu.setVisibility(View.GONE);
llControl.setVisibility(View.VISIBLE);
}

/**
* 拦截物理返回键
*/
@Override
public void onBackPressed() {
if (mWebView.canGoBack()){ // 判断是否可以返回
mWebView.goBack(); // 返回上一个网页
// mWebView.goForward(); 跳到下一个网页(前提是有一个历史记录)
// mWebView.canGoForward(); 判断是否可以跳转到下一个网页
}
else {
finish();
}
}
}

  1. 修改activity_news_detail.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="match_parent"
android:orientation="vertical">

<include layout="@layout/title_bar"/>

<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

<ProgressBar
android:id="@+id/pb_loading3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateDrawable="@drawable/shape_custom_progress"
android:layout_gravity="center"/>

</FrameLayout>
</LinearLayout>

  1. 修改NewsDetailActivity,获取进度条组件的实例,并调用相应api,代码如下:

package com.example.zhbj;

import android.app.Activity;
import android.graphics.Bitmap;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ProgressBar;

import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.view.annotation.ViewInject;

/**
* 新闻详情页
*/
public class NewsDetailActivity extends Activity {

/**
* 返回按钮
*/
@ViewInject(R.id.btn_back)
private ImageButton btnBack;

/**
* 菜单按钮
*/
@ViewInject(R.id.btn_menu)
private ImageButton btnMenu;

/**
* 包含两个功能按钮的线性布局
*/
@ViewInject(R.id.ll_control)
private LinearLayout llControl;

/**
* 字体大小按钮
*/
@ViewInject(R.id.btn_textsize)
private ImageButton btnTextSize;

/**
* 分享按钮
*/
@ViewInject(R.id.btn_share)
private ImageButton btnShare;

/**
* WebView的实例
*/
@ViewInject(R.id.webview)
private WebView mWebView;

/**
* 进度条实例
*/
@ViewInject(R.id.pb_loading3)
private ProgressBar pbLoading;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_news_detail);
ViewUtils.inject(this);
initViews();

// 获取WebView的设置对象
WebSettings settings = mWebView.getSettings();
settings.setJavaScriptEnabled(true); // 启用js功能
settings.setBuiltInZoomControls(true); // 启用显示放大/缩小的按钮,不支持已经适配好移动端的页面
settings.setUseWideViewPort(true); // 启用双击缩放
// 给WebView设置一个监听
mWebView.setWebViewClient(new WebViewClient(){
/**
* 页面开始加载
* @param view
* @param url
* @param favicon
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
pbLoading.setVisibility(View.VISIBLE);
}

/**
* 跳转链接
* @param view
* @param url
* @return
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 强制所有跳转链接都在WebView执行
mWebView.loadUrl(url);
return true;
}

/**
* 加载结束
* @param view
* @param url
*/
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
pbLoading.setVisibility(View.GONE);
}
});
mWebView.setWebChromeClient(new WebChromeClient(){

/**
* 获取网页标题
* @param view
* @param title
*/
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
}

/**
* 进度发生变化
* @param view
* @param newProgress
*/
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
}
});

String url = getIntent().getStringExtra("url");

// 开始加载网页
mWebView.loadUrl(url);
}

/**
* 初始化布局
*/
private void initViews() {
btnBack.setVisibility(View.VISIBLE);
btnMenu.setVisibility(View.GONE);
llControl.setVisibility(View.VISIBLE);
}

/**
* 拦截物理返回键
*/
@Override
public void onBackPressed() {
if (mWebView.canGoBack()){ // 判断是否可以返回
mWebView.goBack(); // 返回上一个网页
// mWebView.goForward(); 跳到下一个网页(前提是有一个历史记录)
// mWebView.canGoForward(); 判断是否可以跳转到下一个网页
}
else {
finish();
}
}
}


举报

相关推荐

0 条评论