Day04 01.昨天内容总结 ##
Day04 02.下拉刷新实现&刷新状态更新 ##
昨天实现了隐藏下拉刷新头布局,现在实现下拉时显示下拉刷新布局
要响应onTouchEvent事件,RefreshListView中重写onTouchEvent方法,里边进行switch判断ev.getAction()
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = (int) ev.getY();//获取开始的Y坐标
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1) {
startY = (int) ev.getY();
}
// 如果当前正在刷新, 不做任何处理
if (mCurrentState == STATE_REFRESHING) {
break;
}
int endY = (int) ev.getY();//获取结束的Y坐标
int dY = endY - startY;// 计算Y方向移动偏移量
int firstVisiblePosition = getFirstVisiblePosition();// 查看第一个显示的item属于第几个
// LogUtils.d("firstVisiblePosition=" + firstVisiblePosition);
if (dY > 0 && firstVisiblePosition == 0) {//下拉动作&当前在listview的最顶部时,才让下拉
int paddingTop = dY - mHeaderHeight;
if (paddingTop < 0 && mCurrentState != STATE_PULL_TO_REFRESH) {//当前不是下拉刷新时,更新为下拉刷新状态
mCurrentState = STATE_PULL_TO_REFRESH;
refreshHeaderViewState();
} else if (paddingTop >= 0 && mCurrentState != STATE_RELEASE_TO_REFRESH) {//进入松开刷新状态
mCurrentState = STATE_RELEASE_TO_REFRESH;
refreshHeaderViewState();
}
mHeaderView.setPadding(0, paddingTop, 0, 0);// 设置头布局padding
return true;
}
break;
case MotionEvent.ACTION_UP:
startY = -1;// 重新初始化起始坐标位置
if (mCurrentState == STATE_RELEASE_TO_REFRESH) {//松开手时当前状态为松开刷新时,更新为正在刷新
// 将当前状态更新为正在刷新
mCurrentState = STATE_REFRESHING;
mHeaderView.setPadding(0, 0, 0, 0);
refreshHeaderViewState();
} else if (mCurrentState == STATE_PULL_TO_REFRESH) {
mHeaderView.setPadding(0, -mHeaderHeight, 0, 0);// 隐藏头布局
}
break;
default:
break;
}
return super.onTouchEvent(ev);
}
1.在按下的case MotionEvent.ACTION_DOWN中获取开始的Y坐标,并抽取成全局变量,初始值为-1,表示是初始的值
只需让"头条新闻ViewPager+普通新闻ListView"上下滑动时,下拉刷新头布局出来,只需要记录Y坐标
2.在移动的case MotionEvent.ACTION_MOVE中
1)获取结束的Y坐标,并计算Y方向偏移量
2)下拉刷新下拉时被头条新闻ViewPager拦截事件的解决
按住头条新闻ViewPager下拉时,ACTION_DOWN会被头条新闻消费,所以需要判断startY是否等于-1
是,说明ACTION_DOWN没走进去,需要重新获取startY,否,反之
以前不需要这个判断,这里需要
因为普通新闻listView中的头条新闻ViewPager也可以滑动
按住普通新闻listview下拉时下拉刷新头布局可以出来但
按住头条新闻ViewPager下拉时也希望下拉刷新头布局能出来
但头条新闻ViewPager是父控件“头条新闻ViewPager+listView”的孩子,默认让孩子消费事件消
所以按住头条新闻ViewPager下拉时事件会被头条新闻ViewPager的ACTIONDOWN消费掉
就走不到RefreshListView的ACTIONDOWN中获取startY了
所以需要在RefreshListView的ACTION_MOVE中重新获取startY
上边是起始点,下边Y坐标值比上边Y坐标值大,所以从上往下拉dy肯定大于0
3)判断dy大于0 且 只有在最顶端下拉时才让下拉刷新布局出来
手机卫士分页中判断过页面有没有到最底部,当时是getLastVisiblePosition拿到当前ACTION最后一个item的显示位置
上拉一会listView后,使listview处于中间位置,再下拉也是在下拉,但getFirstVisiblePosition获取的可见item位置都不知道是第几个item了,,这时下拉刷新控件不需要出来
只有firstVisiblePosition等于0才是最顶端位置
所以不仅要判断dy大于0,并且firstVisiblePosition等于0时才让下拉刷新布局出来
4)下拉刷新布局出来且下拉完后获取下拉移动的paddingTop值
下拉移动的paddingTop应该是 当前下拉刷新的高度 减去 Y方向已移动距离(dy),但隐藏的距离是负值
所以 下拉刷新paddingTop = dy-高度
int paddingTop = dy - mHeaderViewHeight;// 计算当前控件的padding值
将paddingTop设置给mHeaderView去更新位置,这时mHeaderView高度就变成paddingTop了
在ACTION_MOVE的最后return一个true,表示把这个事件消费了
打印下paddingTop的值
运行程序,下拉就能出来了,paddingTop是负值,越来越大,下拉刷新完全展示时paddingTop变成了0
下拉刷新完全展示后再往下拉,PaddingTop绝对值越来越大,多出来的是被拉出来的白色部分
3.在抬起的case MotionEvent.ACTION_UP中
要将startY值重新置为负一来重新初始化起始坐标位置,以防下次再下拉时,startY值会误判
运行程序,下拉出来放开后没缩回去
4.在成员变量处分别用整数1,2,3表示3种状态,即:
private static final int STATE_PULL_TO_REFRESH = 1;// 下拉刷新状态
private static final int STATE_RELEASE_TO_REFRESH = 2;// 松开刷新状态
private static final int STATE_REFRESHING = 3;// 正在刷新状态
用一个成员变量来表示当前是什么状态,默认为下拉刷新状态,即:
private int mCurrentState = STATE_PULL_TO_REFRESH;// 当前状态,默认是下拉刷新
5.在onTouchEvent中根据paddingTop & mCurrentState 判断是“下拉刷新”还是“松开刷新”,“正在刷新”状态
1)case MotionEvent.ACTION_MOVE中,paddingTop < 0 && mCurrentState != STATE_PULL_TO_REFRESH时将mCurrentState置为“下拉刷新”STATE_PULL_TO_REFRESH
判断paddingTop小于0 且 当前状态不是下拉刷新状态,才更新为下拉刷新状态,如果是就不用再更新为下拉刷新状态
2)case MotionEvent.ACTION_MOVE中,paddingTop >= 0 && mCurrentState != STATE_RELEASE_TO_REFRESH时将mCurrentState置为“松开刷新"STATE_RELEASE_TO_REFRESH
3)case MotionEvent.ACTION_UP中,mCurrentState == STATE_RELEASE_TO_REFRESH时,将mCurrentState置为"正在刷新"STATE_REFRESHING
padding放在判断前后都无所谓,到最后统一都要更新这个padding值,即:
mHeaderView.setPadding(0, paddingTop, 0, 0);// 设置控件padding,更新位置
6.pull_to_refresh_header.xml中给每个控件加id,在RefreshListView的initView中初始化控件,声明成全局,即:
tvTitle = (TextView) mHeaderView.findViewById(R.id.tv_title); //标题
tvTime = (TextView) mHeaderView.findViewById(R.id.tv_time); //时间
ivArrow = (ImageView) mHeaderView.findViewById(R.id.iv_arrow);//箭头
pbLoading = (ProgressBar) mHeaderView.findViewById(R.id.pb_loading);//圆形进度条
然后到refreshState方法的三个case中分别给控件设置值
7.箭头从下拉刷新变为松开刷新时,从逆时针旋转180度,松开刷新放开时,又顺时针旋转180度变为下拉刷新
是箭头的旋转动画RotateAnimation动画效果,旋转动画在这里是两种,一种从上向下,一种从下向上
1)在RefreshListView中写initAnim方法,初始化箭头动画,即:
//初始化箭头动画
private void initAnim() {
// 箭头向上
animUp = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);
animUp.setDuration(200);//动画运行时间
animUp.setFillAfter(true);// 保持动画结束后的状态
// 箭头向下
animDown = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF,0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animDown.setDuration(200);
animDown.setFillAfter(true);// 保持动画结束后的状态
}
new一个RotateAnimation叫做animUp,代表箭头向上动画,从0度(fromDegrees)旋转负的180度(逆时针)(toDegrees)
类型pivotXType一般都是Animation.RELATIVE_TO_SELF,值pivotXValue,pivotYValue一般都是0.5居于正中心旋转
动画运行时间setDuration为200毫秒,箭头本来是向下,往上旋转后,一直在上边,所以要setFillAfter(true)保持住动画状态
animDown动画相反,此处略
2)initAnim方法在初始化界面的initView方法中调用
3)到refreshState方法中根据3种状态用箭头ivArrow分别startAnimation()传参为animUp或animDown或setVisibility(View.INVISIBLE)
8.专门写个“根据当前状态,刷新控件”的refreshState方法,根据mCurrentState状态更新箭头方向,文字和进度条是否显示:
//根据当前状态,刷新控件
private void refreshState() {
switch (mCurrentState) {
case STATE_PULL_TO_REFRESH:
tvTitle.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.startAnimation(animDown);
break;
case STATE_RELEASE_TO_REFRESH:
tvTitle.setText("松开刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.startAnimation(animUp);
break;
case STATE_REFRESHING:
tvTitle.setText("正在刷新...");
pbLoading.setVisibility(View.VISIBLE);
ivArrow.clearAnimation();// 必须先清除动画,才能够隐藏控件
ivArrow.setVisibility(View.INVISIBLE);
break;
default:
break;
}
}
switch判断mCurrentState
1)如果是下拉刷新STATE_PULL_TO_REFRESH,文字tvTitle应该setText("下拉刷新"),进度条pbLoading应该setVisibility(View.INVISIBLE)不可见,箭头ivArrow应该startAnimation(animDown)向下
2)如果是松开刷新STATE_RELEASE_TO_REFRESH,文字tvTitle应该setText("松开刷新"),进度条pbLoading应该setVisibility(View.INVISIBLE)不可见,箭头startAnimation(animUp)向上
3)如果是正在刷新STATE_REFRESHING,文字tvTitle应该setText("正在刷新"),进度条pbLoading应该应该setVisibility(View.VISIBLE)可见,箭头ivArrow先clearAnimation清除动画,再setVisibility(View.INVISIBLE)隐藏箭头动画
箭头本身有动画效果,而且保持了动画状态,有动画没法隐藏,必须先清除动画才能隐藏
任何控件只要有动画,让它显示和隐藏就必须先把动画清除掉
9.refreshState分别在onTouchEvent中切换状态的地方调用(每个状态的判断中)来刷新界面
在case MotionEvent.ACTION_MOVE中
mCurrentState = STATE_PULL_TO_REFRESH下边调用
mCurrentState = STATE_RELEASE_TO_REFRESH下边调用
mCurrentState = STATE_REFRESHING下边调用
小问题解决:
1)解决箭头和进度条同时显示的问题
运行程序,往下拉时,箭头和进度条一起显示出来了
进度条默认要隐藏掉,刚进来第一次往下拉时还没有切状态,不会走refreshState方法
到pull_to_refresh_header.xml中,给ProgressBar添加属性: android:visibility="invisible"
2)如果当前是下拉刷新,松开手时不用显示正在刷新,但在松开刷新时松开手,要显示正在刷新
这个逻辑是在手势抬起后才进行,需要在case MotionEvent.ACTION_UP判断, 如果当前是松开刷新状态,即如果mCurrentState等于STATE_RELEASE_TO_REFRESH时
要切换为正在刷新,即mCurrentState等于STATE_REFRESHING,否则(else),如果(if)抬起手了
当前状态mCurrentState还是等于下拉刷新的状态STATE_PULL_TO_REFRESH,那就将下拉刷新布局隐藏掉
3)设置mHeaderViewHeight隐藏布局和设置0完整展示布局
怎么去完整展示状态?
隐藏,设置了负padding用于隐藏掉了
正常展示 将padding全改为0就刚好正常展示
mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);//隐藏布局
mHeaderView.setPadding(0, 0, 0, 0);//完整展示布局
这就是鼠标抬起后(ACTION_UP)的操作
4)正在刷新时不许上下滑动的实现
正在刷新时,有可能还会上下滑动,它会又变成下拉刷新,松开刷新,正在刷新时应该静静等着
在ACTION_MOVE中移动时,判断当前状态mCurrentState是正在刷新状态STATE_REFRESHING
就什么都不做,直接break跳出循环后,它后边就自己return super.onTouchEvent()了
运行程序,只有ListView,往上滑没反应,上边没有任何反应,这就是正在刷新的过滤
5)RefreshListView的onTouchEvent返回值不应该return true的原因
RefreshListView的父控件是listView,listView要处理上下滑动事件
此处光为了展示下拉刷新控件就return true消费事件,listView就无法响应上下滑事件了,需要让listview本身的滑动效果也能起作用
此处应 return super,super可能是false
6)不能在正在刷新的break后边直接return true
不能在RefreshListView的onTouchEvent如下地方break后边直接return true
// 如果当前是正在刷新状态, 什么都不做
if (mCurrentState == STATE_REFRESHING) {
break;// 跳出循环
}
在这ruturn true和在onTouchEvent下边ruturn true没啥区别
在这return true同样会导致listView没有触摸事件,无法上下滑动
在这break跳出循环后在onTouchEvent的下边就return了,我们不参与了
7)需要在下拉动作和ListView最顶部判断中return ture
RefreshListView的onTouchEvent的下拉动作&当前在listview的最顶部判断中
if (dy > 0 && firstVisiblePosition == 0)
在这要return true,这种地方不希望listView参与进来,要全权去处理控件下拉刷新状态,这种情况应该让我自己处理
Day04 03.自定义进度条#
已实现基本的下拉刷新效果,进度条太丑,自定义一下
自定义下拉刷新功能中的进度条为渐变颜色
drawable文件中写shape_custom_progressbar.xml,完整项目进度条是圆环
选shape属性的ring
圆环有渐变颜色gradient
起始颜色startColor 白色#fff
中间颜色centerColor #9f00
终点颜色endColor 红色#f00
渐变方式type有 线性渐变linear,雷达辐射式渐变radial,横扫式渐变sweep
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360" >
<shape
android:innerRadius="15dp"
android:shape="ring"
android:thickness="5dp"
android:useLevel="false" >
<gradient
android:centerColor="#9f00"
android:endColor="#f00"
android:startColor="#fff"
android:type="sweep" />
</shape>
</rotate>
在pull_to_refresh_header.xml中的progressbar添加如下如下属性
android:indeterminateDrawable="@drawable/shape_custom_prgressbar"
indeterminateDrawable可以设置进度条图片
运行程序,发现正在刷新的圆环还有问题,之所以这样转是progressbar自带的效果
在shape_custom_progressbar.xml中添加android:useLevel="false"去掉这个自带效果
这个属性表示要不要层级结构,设置false相当于不用它原来的那个动画了
用rotate旋转标签包裹shape,给rotate标签添加如下属性
android:fromDegrees="0"
android:toDegrees="360"
从0度到360度无死角旋转
android:pivotX="50%"
android:pivotY="50%"
旋转的相对位置为中心点
运行程序,往下拉,正在刷新的圆形进度条就可以了
完整项目中圆环更细
shape_custom_progress.xml的rotate中按alt+/提示
shape中有
innerRadius 内径(内环半径)写个15dp
thickness 厚度写个5dp
Day04 04.下拉刷新回调接口#
到TabDetailPager中去刷新服务器获取数据,在展示正在刷新时去刷新数据
下拉刷新RefreshListView知道什么时候变成正在刷新,下拉刷新,但TabDetailPager不知道
为了让TabDetailPager知道又要用到回调
调用下拉刷新回调接口实现刷新数据功能
1)RefreshListView中写一个下拉刷新回调接口OnRefreshListener,里边定义onRefresh方法,这就是下拉刷新的回调
写setOnRefreshListener方法中以参数形式传入OnRefreshListener,用mListener等于listener接收,并声明OnRefreshListener为mListener
//下拉刷新回调接口
public interface OnRefreshListener {
// 下拉刷新回调
public void onRefresh();
// 加载更多回调
public void loadMore();
}
private OnRefreshListener mListener;
public void setOnRefreshListener(OnRefreshListener listener) {
mListener = listener;
}
2)在RefreshListView的鼠标抬起后(ACTION_UP),切为正在刷新(mCurrentState = STATE_REFRESHING)时,
用mListener.onRefresh()回调这个下拉刷新回调接口
先判mListener不为null时,才去回调,这就是回调下拉刷新的动作
// 回调下拉刷新动作
if (mListener != null) {
mListener.onRefresh();
}
}
3)TabDetailPager的initView中,给lvList设置setOnRefreshListener刷新事件,并重写onRefresh()方法去调用getDataFromServer()从服务器获取数据
lvList.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh() {
getDataFromServer();
}
});
运行程序,下拉刷新,数据过来了(在logcat中打印了‘页签页解析结果’)
但是正在刷新布局没有收回去,TabDetailPager知道在刷新完后要收回去,但是RefreshListView控件不知道
调用下拉刷新回调接口实现收起正在刷新布局的功能
TabDetailPager的getDataFromServer的onSuccess中刷新完要通知RefreshListView收回下拉刷新控件
1)RefreshListView中再写一个onRefreshComplete方法,专门收起正在刷新布局
刷新结束就是把padding重新隐藏掉,同时把很多成员变量初始化下
//当刷新完成后,隐藏下拉刷新控件, 初始化各项数据
public void onRefreshComplete(boolean success) {
System.out.println("isLoadingMore:" + isLoadingMore);
if (!isLoadingMore) {
// 隐藏控件
mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
// 设置为默认的下拉刷新状态
mCurrentState = STATE_PULL_TO_REFRESH;
tvTitle.setText("下拉刷新");
pbLoading.setVisibility(View.INVISIBLE);
ivArrow.startAnimation(animDown);
if (success) {
setRefreshTime();// 网络访问成功,才设置刷新时间
}
} else {
// 加载更多
isLoadingMore = false;
mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);// 隐藏更多
}
}
2)TabDetailPager的getDataFromServer的onSuccess中调用
// 收起加载更多控件
lvList.onRefreshComplete(true);
运行程序,往下滑,手指放开就收起来了,这是我们收起的一个操作
3)还要考虑服务器异时走到onFailure方法
数据获取失败,Toast提示用户,控件也要收起,这样用户能继续刷新去重试
在onFailture方法中写
// 收起加载更多控件
lvList.onRefreshComplete(false);
Toast.makeText(mActivity, "加载更多失败", Toast.LENGTH_SHORT).show();
Day04 05.更新刷新时间 ##
实现下拉刷新下边的时间功能,这个时间指的是上次的刷新时
给下拉刷新添加上次刷新成功的时间功能
1)在下拉刷新RefreshListView中再写一个setRefreshTime方法设置时间,就是给tvTime(TextView)去setText
每次设置的是当前刷新时间,格式为2015-09-01 17:12,new一个SimpleDateFormat(pattern)对时间格式化
参数partern要的是年月日格式
年是yyyy,月是大写的MM,日是dd,时是HH,分是mm,秒是ss
为什么有时是大写有时是小写?
老外搞年月日时,认为1月份是从0开始,0是1月份,1是2月份
如果是小写m返回的一月份就是0, 用大写就表示从1开始不要从0开始
大写H表示是24小时制,小写h表示是12小时制
mm和ss之所以两位,是因为个位要补位
对时间格式化时new一个Date,默认就是当前时间,倒一下包java.util.Date
返回String类型的time,设置给tvTime
private void setRefreshTime() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// H大写表示24小时制;h表示12小时制
String time = format.format(new Date());
tvTime.setText(time);
}
在RefreshListView的initView中一上来就调用setRefreshTime先去设置下当前时间
在onRefreshComplete中每次刷新之前,也调用下setRefreshTime去设置下当前时间
ctrl+o可以在.java中查看它的所有方法
运行程序,往下滑,时间变成9月2号2点43(模拟器中的时间),再刷新时时间就变成45了
2)这个相当于每次下拉刷新刷新完后(RefreshListView的onRefreshComplete方法),就把时间设置了一下
但万一刷新失败了,也会走onRefreshComplete方法也把时间给设置了一下
失败了不设置时间,这个指的是上次刷新成功的时间
给onRefreshComplete加个参数,表示成功还是失败,成功才设置时间
public void onRefreshComplete(boolean success){
if (success) {
setRefreshTime();// 网络访问成功,才设置刷新时间
}
}
在TabDetailPager的getDataFromServer的onSuccess的lvList.onRefreshComplete(true)传一个true
在onFailure方法的lvList.onRefreshComplete(false)传一个false
Day04 06.下拉加载更多-脚布局实现 ##
实现下拉刷新功能时不支持用相对布局作为根布局
写下拉刷新控件pull_to_refresh_header.xml时不能用相对布局作为根布局
否则运行就会挂掉,报xml文件有问题
给一个listView添加headerView时,不支持用相对布局作为根布局
相对布局本身有一个bug或问题,所以在写listview头布局时尽量用线性布局
通过给ListView加脚布局的形式实现下拉加载更多效果
下拉刷新RefreshListView中initView表示初始化头布局,为了和脚布局分开,重命名为initHeaderView
刚才这个动作就叫重构,写代码不可能一次性写的非常好,根据需求给新加功能对原来方法名或结构修改就叫重构
再写一个初始化脚布局的initFooterView,也应该在每次上来就初始化,即在RefreshListView的三个构造方法中调用
在layout文件夹中写pull_to_refresh_footer.xml,它是progressbar + textView
可以从pull_to_refresh_header.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:gravity="center"
android:orientation="horizontal" >
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminateDrawable="@drawable/shape_custom_prgressbar" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:text="加载中..."
android:textColor="#f00"
android:textSize="18sp" />
</LinearLayout>
RefreshListView的initFooterView中,View.inflate布局,并抽取成全局变量
调用addFooterView(mFooterView)将脚布局添加给RefreshListView
//初始化脚布局
private void initFooterView() {
mFooterView = View.inflate(getContext(),
R.layout.pull_to_refresh_footer, null);
this.addFooterView(mFooterView);
// 隐藏脚布局
mFooterView.measure(0, 0);
mFooterViewHeight = mFooterView.getMeasuredHeight();
mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);
this.setOnScrollListener(this);
}
运行程序,看到把脚布局添加加进来了
添加进来后,接下来实现隐藏脚布局,脚布局隐藏方式和和头布局方式完全一样
隐藏上拉加载脚布局的实现
mFooterView先measure测量一下
再去getMeasuredHeight()拿到它的高度
高度拿到后就可以给mFooterView去setpadding隐藏了
代码如上
Day04 07.下拉加载更多实现 ##
实现了隐藏脚布局的功能后,当用户滑到最底部时,应该显示脚布局
上拉时显示上拉加载布局的实现
监听页面滑动到底部的事件,手机卫士分页功能就有监听滑动到底部
RefreshListView类名处,implements OnScrollListener,导包,实现它的方法去设置滑动事件
initFooterView中setOnScrollListener给RefreshListView设置回调监听
传this就可以了,因为当前类已经implements实现了它
this.setOnScrollListener(this);
实现它后一旦页面滑动到底部就会走如下两个方法
onScrollStateChanged 表示滑动状态发生变化
onScroll 滑动过程中会回调onScroll方法,不需要这个方法
这里需要的是onScrollStateChanged,它会回传一个scrollState
ctrl+鼠标左击OnScrollListener,进入AbsListView$OnScrollListener.class
看到有3种状态:
SCROLL_STATE_IDLE = 0; 空闲状态
SCROLL_STATE_TOUCH_SCROLL = 1; 刚开始触摸状态
SCROLL_STATE_FLING = 2; 抛飞状态,listView刷一下一滑,它就跑上去了,这就叫飞的动作
看列表静下来结束后是否到最底部,所以看IDLE事件就可以
RefreshListView的onScrollStateChanged中判断scrollState等于SCROLL_STATE_IDLE表示listview是空闲状态,这时看下有没有到最底部
getLastVisiblePosition()拿到当前界面显示的最后item位置
如果这个位置刚好是listview的最后一个元素,就认为它到最后一个了
那就再判断如果lastVisiblePosition等于getCount()减1,就是最后一个,并打印日志“到底了”
if (scrollState == SCROLL_STATE_IDLE) {// listview空闲
int lastVisiblePosition = getLastVisiblePosition();// 当前显示的最后一个item的位置
if (lastVisiblePosition == getCount() - 1 && !isLoadingMore) {
// 最底部了
System.out.println("到底了...");
}
}
这就是监听页面到底部了的事件,运行程序
有一个真机的在线链接,把这些tomcat服务器代码全放在了在线服务器上,是哪个地址?
//服务器线上前缀地址
public static final String SERVER_URL ="http://zhihuibj.sinaapp.com/zhbj";
这个是我专门申请了一个域名,叫做zhihuibj.sinaapp.com,是新浪云平台上的域名
你只需要把
// 服务器根地址, 10.0.2.2是预留的ip地址,专供模拟器访问PC的服务器使用
public static String SERVER_URL = "http://10.0.2.2:8080/zhbj";
这个注释掉
//服务器线上前缀地址
把public static final String SERVER_URL ="http://zhihuibj.sinaapp.com/zhbj";
这个打开
这样就无缝切换到线上了,这时拿真机跑就没问题了
放在在线服务器上要花钱,按流量计费,你们没有几个人用,我就不花钱
还是把项目在android原生模拟器上运行,当新闻中心的listView滑到底部时,就打印'到底了'
到底后需要把脚布局显示出来
RefreshListView的onScrollStateChanged方法中,mFooterView去setPadding,完全显示,左右宽高都是0
mFooterView.setPadding(0, 0, 0, 0);// 显示脚布局
运行程序,listview到底了,也在logcat中打了到底了,但是脚布局没有出来,继续往下滑才出来
这个效果比较恶心,滑到底了再往下滑才出来
1.解决滑到底部后再往下滑脚布局才出来的问题(调用setSelection设置当前选中最后一个位置)
用户希望一到底马上出来,之所以需要到底了再次滑才出来,是因为到底后才把脚布局显示出来,那它自然就在下边了
如何不让它在下边,listView有一个设置当前选中哪个位置的方法setSelection(position)
把当前选中的位置设置为最后一个,getCount() - 1表示是最后一个,即:
this.setSelection(getCount() - 1);// 让listview显示在最后一个位置,直接显示脚布局
listview默认是从0开始
如果希望listview从某个位置开始显示,那就调用setSelection(position),把位置传给它即可
这样listview就会自动定位到这个位置
运行程序,往下滑到底部时,不用再往下滑,脚布局就显示出来了,因为手动强制让listview跑到这个位置了
2.保证到底后只加载一次网络数据的实现(添加并判断boolean值isLoadingMore)
到底后应该去加载这个网络数据,但有时网速比较慢,用户比较着急还往下滑呀滑呀,这时会频繁调到底了
频繁的去访问网络数据,会出现很多重复数据,所以应该加一个标记判断是否正在加载
在RefreshListView成员变量处写个isLoadingMore表示是否正在加载更多,默认是false不加载
private boolean isLoadingMore = false;// 是否正在加载更多
onScrollStateChanged方法到底后,isLoadingMore就变为true表示开始加载更多
但只有false的情况下才会走进来,所以在上边判断时,增加‘并且isLoadingMore是false的情况下’,才走进来
然后设置isLoadingMore为true去开始加载更多
它是false才设置为true开始加载,已经开始加载就不用加载了,加这个标记去保证只加载一次
RefreshListView的onScrollStateChanged方法如下:
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// 滑动状态发生变化
if (scrollState == SCROLL_STATE_IDLE) {// listview空闲
int lastVisiblePosition = getLastVisiblePosition();// 当前显示的最后一个item的位置
if (lastVisiblePosition == getCount() - 1 && !isLoadingMore) {
isLoadingMore = true;// 开始加载更多
// 最底部了
System.out.println("到底了...");
mFooterView.setPadding(0, 0, 0, 0);// 显示脚布局
this.setSelection(getCount() - 1);// 让listview显示在最后一个位置,直接显示脚布局
// 访问网络数据
if (mListener != null) {
mListener.loadMore();// 加载更多
}
}
}
}
运行程序,往下滑打印到底了,这时用户再往下滑,就不打印到底了
因为一到底把isLoadingMore设置为true了,false时才走进来,是true就不走进来了,保证到底了只执行一次
访问网络数据是TabDetailPager的逻辑,在RefreshListView肯定没有办法,只能通知TabDetailPager该加载下一页数据了
3.共用下拉刷新回调接口实现加载下一页数据的回调
在RefreshListView的OnRefreshListener回调接口中,再写一个加载更多的回调loadMore方法
在RefreshListView的onScrollStateChanged中访问网络数据时调,判断mListener不等于空才调mListener的loadMore方法去加载更多
这样写后主页面TabDetailPager会报错,因为它少实现了一个加载更多的方法loadMore
到TabDetailPager报错的地方,点击Add unimplemented methods去实现
lvList.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh() {
getDataFromServer();
}
@Override
public void loadMore() {
if (mMoreUrl != null) {
getMoreDataFromServer();
} else {
Toast.makeText(mActivity, "没有更多数据了...", Toast.LENGTH_SHORT).show();
// 收起加载更多控件
lvList.onRefreshComplete(true);
}
}
});
4.实现加载下一页数据的功能
onRefresh中getDataFromServer()加载的是第一页数据
1)loadMore中加载的是下一页数据,写一个getMoreDataFromServer()表示加载下一页数据,并生成方法
//加载下一页数据
protected void getMoreDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpMethod.GET, mMoreUrl, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
// 访问成功
String result = responseInfo.result;// 服务器返回的数据
processData(result, true);
// 收起加载更多控件
lvList.onRefreshComplete(true);
}
@Override
public void onFailure(HttpException error, String msg) {
// 请求失败
error.printStackTrace();
Toast.makeText(mActivity, msg, Toast.LENGTH_SHORT).show();
// 收起加载更多控件
lvList.onRefreshComplete(false);
}
});
}
下一页数据怎么加载
浏览器地址栏中输入localhost:8080/zhbj/10007/list_1.json回车查看数据格式,这是新闻的列表数据,网上大讲堂之类的
每个数据前边有个字段more,就是下一页数据,localhost:8080/zhbj/10007/list_1.json是当前页数据
把浏览器地址栏的/10007/list_1.json改为下一页数据链接/10007/list_2.json,回车就是下一页数据
还是网上大讲堂,但是后边加了个2表示是下一页数据,这个就是第二页数据
第二页数据发现没有more,说明没有下一页数据了more是空字符串
所以加载下一页,就看有没有more字段
2)TabDetailPager解析数据方法processData中有一个newsBean字段,可以从newsBean拿到解析后的data,data有个more就是下一页数据,返回一个String类型的moreUrl
用TextUtils..isEmpty(str)判断它是否为空,不为空就给它拼一个链接
moreUrl只有/10007/list_1.json,没有前缀
写个String叫做mMoreUrl等于GlobalConstants点SERVER_URL加上moreUrl
这是下一页地址,把它声明成成员变量
private String mMoreUrl; //下一页地址
String moreUrl = newsBean.data.more;
if (!TextUtils.isEmpty(moreUrl)) {
mMoreUrl = GlobalConstants.SERVER_URL + moreUrl;
} else {
mMoreUrl = null;// 没有下一页数据了
}
来个else,moreUrl为空时,应该将mMoreUrl置空,表示没有下一页数据了
这就是这样一个processData,以上的就是初始化下一页链接的代码
TabDetailPager的getMoreDataFromServer中,只需要加载MoreUrl就可以了
逻辑和getDataFromServer方法类似
把getDataFromServer方法中的逻辑拷贝到getMoreDataFromServer方法中,把mUrl改为mMoreUrl
它也会设置数据result,缓存不加了,下拉刷新控件也删掉
3)loadMore方法中判断mMoreUrl不为空时调用getMoreDataFromServer方法加载下一页数据
为空说明是最后一页了,Toast提示用户"没有更多数据了..."
TabDetailPager的getMoreDataFromServer中拿到的数据result是下一页的json数据
调用processData对newsBean解析,最后把它放进mNewsList集合,给lvList(listView)去设置Adapter
这样会导致下一页数据把上一页数据覆盖,希望一页之后追加一页,数据越来越多
5.解决下一页数据将上一页数据覆盖的问题(加标记isMore判断是否第一页刚进来)
processData方法参数中追加参数boolean isMore,如果isMore等于false才走这些(头条新闻)逻辑,头条新闻只加载一次就行了,剩下的是新闻列表去分页
这是第一次进来,所以在这判断如果isMore等于false,才走这些逻辑,这是第一页数据初始化
第一页肯定要给它去设置adapter之类的
else表示下一页数据,可以从newsBean拿到新闻数据,这时这个数据是更多的数据moreNews
addAll把moreNews追加给整个集合mNewsList,最后刷新下adapter
else {// 加载更多
ArrayList<News> moreNews = newsBean.data.more;
if (mNewsList != null) {
mNewsList.addAll(moreNews);// 将更多数据追加给现有的集合
mNewsAdapter.notifyDataSetChanged();
}
}
这就是下一页数据的逻辑,加了isMore标记后
getMoreDataFromServer中调用的processData(result)报错了,应该传的是true
processData(result,true);
getDataFromServer中调用的processData(result)报错,这个是第一次进来,肯定传个false
processData(result,false);
initData中调用processData(cache)报错,这个是缓存,缓存是第一页进来肯定要加载个缓存,这时它也是第一次进来,还是false,即: processData(cache,false);
运行程序,往下滑,第二页过来了,再往下滑没了,没了后要把加载中消失掉
现在'加载中...'还在这,往下滑没有弹Toast提示
6.解决没有下一页数据时没有提示“没有更多数据了”
没有弹Toast提示是因为没有走到loadMore中,因为RefreshListView的onScrollStateChanged判断的isLoadingMore是true
现在需要数据返回完后收起加载更多控件
1)TabDetailPager的getMoreDataFromServer的onSucess中添加如下代码:
// 收起加载更多控件
lvList.onRefreshComplete(true);
在onFailure方法中也要收起来,也添加如上代码,只不过把true改为false即可
调这个onRefreshComplete中是收起来下拉刷新头布局,又不能收脚布局
2)在自定义控件RefreshListView的onRefreshComplete中再加一个收起脚布局的功能
判断isLoadingMore是不是在加载更多,不是就隐藏头布局,说明就是下拉刷新
else表示下拉加载更多
把isLoadingMore置为false,已经结束了,方便下次下拉刷新再进来
mFooterView去setPadding设置-mFooterViewHeight隐藏脚布局
else {
// 加载更多
isLoadingMore = false;
mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);// 隐藏更多
}
3)TabDetailPager的getMoreDataFromServer中去收起下拉加载更多控件时
getMoreDataFromServer会走到RefreshListView的onRefreshComplete的else中把加载更多控件隐藏掉
运行程序,点新闻中心往下拉是第二页,再往下拉没了,它就会吐司提示没有更多数据了
但是它没有走进来,即没有把‘加载中...’布局给隐藏掉
下拉刷新下刷新新数据时,下拉刷新的布局都不隐藏了
7.解决没有下一页数据时没有隐藏脚布局‘加载中...’
按理加载更多后应该调一下TabDetailPager的onSuccess的lvList.onRefreshComplete(true)把控件收起来
收起来后,按理说应该走到onRefreshComplete的else把‘加载更多’控件隐藏掉
onRefreshComplete打印下isLoadingMore值对不对,true或false,确定有没有走到onRefreshComplete的else中
System.out.println("isLoadingMore:" + isLoadingMore);
运行程序,打印出来isLoadingMore是true,true就走到RefreshListView的onRefreshComplete的else中把‘加载更多’隐藏掉了,并把isLoadingMore重新置为false,说明第一页加载完后,把‘加载更多’控件收起来了
再往下滑加载下一页数据,打印‘到底了...’,但是没有打印isLoadingMore值
说明到底后没走到RefreshListView的onRefreshComplete中来
onRefreshComplete在TabDetailPager的getMoreDataFromServer中访问网络时调的
现在是最后一页数据,直接到TabDetailPager的loadMore中吐司提示‘没有更多数据了’
根本没走getMoreDataFromServer
因为在TabDetailPager的loadMore忘了调lvList.onRefreshComplete(true)
弹出‘没有更多数据了...’,但没有把‘加载更多...’收起来就是这个原因
传true或者false,没有太多意义,只针对下拉刷新管用,对‘加载更多...’没有任何作用
因为这个只有在RefreshListView的onRefreshComplete的if(!isLoadingMore)这个地方用,所以传true或false,没有任何关系
运行程序,往下滑,吐司提示‘没有更多数据了...’,然后把‘加载更多...’收起来了
切换到中国,中国永远有下一页数据,因为写了一个死循环,第一页的下一页数据是第二页,第二页的下一页数据是第一页,就是为了能够方便看分页效果,这个是无限往下分页了
8.上拉加载更多的逻辑总结
1)在RefreshListView中用initFooterView去初始化脚布局放在最底部
2)在RefreshListView的initFooterView中设置了滑动监听this.setOnScrollListener(this);
会走到RefreshListView的onScrollStateChanged中判断listView空闲后获得最后一个位置
再判断到最底部了就把‘加载更多...’脚布局显示出来
主动去setSelection让它显示在最后一个位置,这样脚布局就直接显示出来了
3)再进行一次回调loadMore,在主页面TabDetailPager中实现loadMore加载下一页数据
TabDetailPager的processData中从页面数据中拿到下一页的链接mMoreUrl
没有moreUrl说明没有下一页,所以在else中把mMoreUrl置为null
有了下一页链接后,就可以在TabDetailPager的getMoreDataFromServer请求下一页数据,直接调mMoreUrl加载后,需要把这次的数据result追加给原来的集合,传个标记isMore,要加载下一页数据就置为true,即: processData(result,true);
传到TabDetailPager的processData中,等于true就在else中追加下一页数据,等于false表示第一页数据,进来之后原来怎么写就还是怎么样,这是下一页数据的逻辑
4)怎么判断的没有下一页数据了?
TabDetailPager的loadMore的mMoreUrl为空就到底了没有下一页数据了,来到了else中,吐司提示‘没有更多数据了...’并收起加载更多控件的布局
TabDetailPager的getMoreDataFromServer中加载下一页时,也调用lvList.onRefreshComplete(true)收起'加载更多...'控件
这是下拉加载更多的逻辑,和手机卫士通讯卫士模块逻辑基本一样
唯一区别是一个是从数据库取数据,一个是从网络调接口取数据
Day04 08.下拉刷新&上拉加载总结 ##
市面上有很多下拉刷新上拉加载的第三方开源框架,逻辑是相似的,面试时可以讲讲,这样有技术含量
只需要把jar包或库文件导进来,就可以迅速实现效果
下拉刷新就是给ListView加头布局,设置负padding隐藏布局,通过监听触摸滑动事件,动态计算当前padding,实时设置最新的padding,设置完后就会慢慢跟随手指下滑出来,这是下拉刷新最核心的
又通过padding值是0,大于0,小于0决定是松开刷新还是下拉刷新,padding到了临界点时马上去更新状态
松开手时就马上切换为正在刷新,刷新控件就做完了
面试官问这就做完了
怎么通知主界面去刷新,主界面怎么知道,不就是个回调,再说回调接口怎么去写
在TabDetailpager中的回调接口setOnRefreshListener中写个onRefresh()的回调方法
再暴露一个接口,让RefreshListView去setOnRefreshListener把接口实现的东西传过来
在RefreshListView控件内部一旦调用mListener.loadMore去下拉刷新
就在TabDetailPager调一下这个回调接口响应onRefresh()方法
然后去通知这个界面,这就是下拉刷新
上拉加载核心代码也是加脚布局,逻辑比下拉刷新简单很多,因为不需要响应触摸事件一点一点出来
一到底部马上就出来,出来后开始加载下一页,加载完后又把它隐藏掉,也是通过回调去通知界面更新
下拉加载复杂的地方是在TabDetailPager把下一页数据追加在集合中,RefreshlistView中不是很复杂
下拉刷新是在TabDetailPager的setOnRefreshListener的onRefresh比较简单,就调getDataFromServer()默认加载第一页数据
但是‘下拉刷新’在RefreshListView的下拉刷新触摸监听这块又有点复杂,所以这两各有千秋
面试中把这个大体思路了解清楚,能说出个六七成就可以
Day04 09.标记已读未读 ##
设置每个页面的点击事件,希望点击后能跳页面或切逻辑做相关处理
在TabDetailPager的initView中给lvList设置点击事件setOnItemClickListener
在onItemClick中打印被点击的位置position
lvList.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
System.out.println("点击位置:" + position);
}
});
运行程序,新闻中心点listview每个item,点第一个位置的网上大讲堂,打印的点击位置是2
因为这有‘下拉刷新’和‘头条新闻ViewPager’这两个头布局作为listview的一部分
下拉刷新是0,‘头条新闻ViewPager’是1,网上大讲堂是2
解决因为加了头布局导致ListView点击事件中position不是0的问题(代理模式)
这时调用mNewsList.get(position)从集合mNewsList中取到的是第三个item
1)因为mNewsList集合是从网上大讲堂开始算的,是第0个
所以在取时要写成: mNewsList.get(position - 2)才能取到正确的值
这样写也可以,但以后将‘头条新闻ViewPager’头布局去掉后,这里还要重新改成-1
重新加了头布局又要重新-3,这种方式太麻烦
2)setOnItemClickListener一点不动的前提下,希望position返回的是已经减掉头布局个数的位置
ctrl+点击setOnItemClickListener到源码中,发现找的是父控件AdapterView.class的setOnItemClickListener,lvList是下拉刷新的RefreshListView,即TabDetailPager的:
@ViewInject(R.id.lv_news)
private RefreshListView lvList;
可以在下拉刷新的RefreshListView中重写setOnItemClickListener方法,进行修改
在TabDetailPager中去setOnItemClickListener时,找的是RefreshListView中重写的setOnItemClickListener
相当于把TabDetailPager的setOnItemClickListener接口对象OnItemClickListener
传递给了RefreshListView的setOnItemClickListener的listener
对RefreshListView的setOnItemClickListener不做任何改动,它直接调的是super.setOnItemClickListener(Listener),把Listener传给它爹AdapterView.class了,其实AdapterView.class回传回来的position数据有问题
不希望直接这样去做,那这时要怎么做?
先把RefreshListView的setOnItemClickListener的super.setOnItemClickListener(Listener);注释掉
就不在回传这个listener了
让RefreshListVie实现android.widget.AdapterView.OnItemClickListener
并实现它的方法onItemClick,setOnItemClickListener
在RefreshListView的setOnItemClickListener中传个this,不再像TabDetailPager传listener了
super.setOnItemClickListener(this);
目的是让RefreshListView去处理点击事件了,不再由TabDetailPager的
lvList.setOnItemClickListener(new OnItemClickListener() {
}的这个去处理点击事件了
在RefreshListView中传个this对象,给下拉刷新控件设置点击事件时,当item被点击后会走到RefreshListView的onItemClick中,我给自己设置点击事件,把自己传了进去,我自己能响应到onItemClick方法
使用回调方式响应item被点击的事件
这个方法中需要通知TabDetailPager,item被点击的事件
listener对象就是RefreshListView中的setOnItemClickListener中的listener
在TabDetailPager中setOnItemClickListener时,把OnItemClickListener对象传给了RefreshListView的setOnItemClickListener的listener,这个listener就是那个对象
这个listener一设置进来,TabDetailPager的listener就响应,现在没有在RefreshListView中设置listener,而是直接将当前的这个对象(this)设置给了它,这其实还是一个回调,把它去维护起来叫做mClickListener
private OnItemClickListener mClickListener;
在RefreshListView的setOnItemClickListener方法中写
mClickListener = listener;
这样就把listener放在mClickListener了
一旦item被点击后走到onItemClick中,在这个方法中再去回调
判断mClickListener不为空时去通知下,调onItemClick接口
onItemClick传什么参数这就传什么参数,但postion在这应该position减getHeaderViewsCount()
mClickListener.onItemClick()回调后,TabDetailPager的setOnItemClickListener就能拿到这个position
RefreshListView这个地方对position偷梁换柱进行了修改
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
// 通知前端界面,item被点击的事件, 回调
if (mClickListener != null) {
// 将position减掉头布局个数之后,回调给前端界面
mClickListener.onItemClick(parent, view, position- getHeaderViewsCount(), id);
}
}
这句话是将position减掉头布局个数后回调给TabDetailPager
这时一回调,TabDetailPager就马上调onItemClick,TabDetailPager的onItemClick的position就是已经被减掉头布局的位置
TabDetailPager的这个方法什么都没动,只是在底层转了一圈
运行程序,点击网上大讲堂,打印的点击位置是0了,这样值就正常了,这就是偷梁换柱的代理模式操作
用代理模式对点击事件的处理和封装(最本质的还是回调)
TabDetailPager在setOnItemClickListener时设置的是listView的父类adapterView,它底层设置的是它的setOnItemClickListener
现在把这条路线给咔嚓掉
去设置listener时,把路径改到RefreshListView,让RefreshListView重写了setOnItemClickListener
让RefeshListView自己去设置一个点击事件,一旦点击后,点击事件就由AdapterView回传给RefreshListView,这时回传的position之类的信息都要回传回来
RefreshListView拿到这个事件之后,在它的OnItemClick中肯定会走到onItemClick中,把这个参数又回传给了TabDetailPager,但是在回传过程中对position进行了偷梁换柱,但是TabDetailPager本身并不知情
相当于做了一个代理,本来TabDetailPager可以直接给AdapterView去设置,现在是让RefreshListView去响应的点击事件,完了之后再通知TabDetailPager和AdapterView
不直接在TabDetailPager的setOnItemCLickListner中减2而是采用代理模式解决的原因:
写api,自定义控件等框架时,要全心全意为开发者服务,调用自定义控件的人考虑的越少,说明自定义控件写的越牛逼
这就是简单的封装,底层虽然做了很多工作,但是封装出来暴露给开发者的接口很简单,开发人员不用考虑太多东西
完整项目,点击‘网上大讲堂’item后,item变成灰色表示标记已读状态,下次再进来还是已读状态
item被点击后,在本地保持住已读的状态
标记列表item已读未读功能
TabDetailPager的setOnItemClickListener的onItemClick中标记已读
浏览器中的json数据,每条对象是一个已读对象,对象中有个id表示这条新闻的唯一标示
‘网上大讲堂’id:35311,‘马路改建’id:35312
可以拿这个id作为新闻标识,一旦‘网上大讲堂’被点击,就把id保存在sp,下次再进来sp中有这个id,就认为是已读状态,
key就叫做read_ids,表示可以有好几个新闻同时已读,values就是它的id
如果35312表示已读,加一个逗号,再写35312,一直追加
sp中只能使用read_ids这一个字段,这个字段中的值越来越多一直在更新,可以把id保存在read_ids这样一个字段中
先从新闻列表拿到当前的新闻对象
News news = mNewsList.get(position);
从新闻对象news中可以拿到id,,在原来的基础上再去追加
要知道sp目前是什么结果,才能在最新结果后边追加
用PrefUtils去getString,key叫做read_ids,values默认是空串,返回String叫做readIds
String readIds = PrefUtils.getString(mActivity, "read_ids", "");
在readIds基础上进行追加,readIds等于readIds加上最新的news.id,再加逗号,即:
readIds = readIds + news.id + ",";// 将已读新闻id追加到已读ids中
用PrefUtils.putString把更新后的readIds重新保存起来,key还是read_ids,value已经是新的value了,已经是readIds了
PrefUtils.putString(mActivity, "read_ids", readIds);
分析逻辑,先拿到目前的read_ids,如果第一次进来返回的readIds是空串,让空串等于空串加35311再加一个逗号
结果就是'35311,',下次再进来时'35311,'等于‘335311,’加上新的'35312',后边再加逗号
即'335311,35312,',这样每次屁股后边跟个逗号
所以为什么说在这写个空串"",如果写个null空指针就很难控制了,在这就没法再加了,还得判空很烦
直接写个空串就搞定了,这就是我们去标记它已读未读的这样一个操作
运行程序,新闻中心点‘网上大讲堂’,再点‘马路改建’,data/data/com.itcast.zhxa02/config.xml
把它导出来打开后ctrl+f搜索下read_ids,找到read_ids是35311,35312
但是有个bug,比如'网上大讲堂',点很多次,config.xml导出来看到35311很多,记录一次标记是已读就够了
判断有没有,没有才追加,有就不再追加了
如果readIds中已经contains(cs)包含了新闻id,即news.id,就不追加了,不包含才追加
News news = mNewsList.get(position);
String readIds = PrefUtils.getString(mActivity, "read_ids", "");
if (!readIds.contains(news.id)) {// 已读ids中不包含当前id,才进行追加
readIds = readIds + news.id + ",";// 将已读新闻id追加到已读ids中
PrefUtils.putString(mActivity, "read_ids", readIds);
}
mNewsAdapter.notifyDataSetChanged();
运行程序,新闻中心多次点'网上大讲堂',config.xml的read_ids只记录了一次,这是已读的标记
标记完后,应把item颜色换成灰色,在这个地方去刷新下adapter,刷新listview,更新已读状态
notify时getView方法可以重新走一次,在getView方法中判断已读未读
// 判断已读未读
String readIds = PrefUtils.getString(mActivity, "read_ids", "");
if (readIds.contains(news.id)) {
// 标题置灰
holder.tvTitle.setTextColor(Color.GRAY);
} else {
// 标题置黑
holder.tvTitle.setTextColor(Color.BLACK);
}
这是刷新已读未读的界面,点击item时,马上去刷新listView更新已读状态,即上边在onItemClick中写的:
mNewsAdapter.notifyDataSetChanged();
这时listView根据最新readIds,重新把每个item过了一遍
运行程序,点新闻中心,刚进来'网上大讲堂'已经是灰色了,因为listview第一次设置数据时就发现第一个item'网上大讲堂'已经是灰色了,再点'马路改建',也变成灰色了
将标记已读未读的全局刷新改为局部刷新可大幅度提高性能
在这个基础上进行优化,点的是这一个item,但是这一个item一点后,其他item也要去刷新一下,有点浪费性能
没有必要进行全部刷新,可以实现一个局部刷新,把上边在onItemClick中写的
mNewsAdapter.notifyDataSetChanged();注释掉
点'北京两年'item,找到这个里边的textView,把textView颜色换一下,这就是局部刷新
当前被点击的item的View对象就是onItemClick方法参数中的view,当lvList去setOnItemClickListener时
它会回传onItemClick参数中的parent,view,position,id
parent: 是AdapterView,listView继承的是AdapterView,所以parent本身就是listview对象,就是lvList
view: 指的是当前被点击的那个item的布局对象
position: 位置
id: 是id
所以view参数就是被点击的item布局对象,拿到被点击的布局对象后去findViewById,就能拿到TextView(‘北京两年内将迁出1200家工业污染企业’这个textview),所以在setOnItemClickListener方法中找到textview
// 局部刷新
TextView tvTitle = (TextView) view.findViewById(R.id.tv_title);
tvTitle.setTextColor(Color.GRAY);
这样性能就很高了,直接找到view对象设置成灰色就行
局部刷新的3种实现方式
1.给那个view去setTag打个标记,再去findViewByTag,用tag把那个view拿到手
2.还有一个方式是getTag方式,这个方式没有必要,很麻烦的,得每个View都维护一个Tag
3.onItemClick方法的参数view就是当前被点击item的view对象,还干嘛要用findViewByTag
运行程序,效果完全一样,点击‘大雾再锁京城’item,只是把自己刷新了,没有刷新其他item
通过这个view去findViewById拿到对应的textview设置成灰色就行了
Day04 10.跳转新闻详情页面 ##
完整项目点击item后跳页面,在com.itcast.zhxa02包中创建新闻详情页NewsDetailActivity继承自Activity
并在清单文件注册,在onCreate方法中加载布局文件activity_news_detail.xml
布局文件上边有一个标题栏,标题栏前边已经抽取出来了,直接include引入进来就行
预览到这个标题栏和我们要的不太一样
有2种手段
1.自己重新打造一个新的标题栏
2.重用以前的标题栏,对以前的标题栏进行修改
这里用第二种方式,在左侧有时候是返回按钮,有时候是menu菜单,把title_bar.xml的ImageButton再抄一份,写两个ImageButton,位置完全一样压在一起,修改第二个ImageButton的id为btn_back,把src引用的图片换成back.png
返回键默认应该是隐藏,给第二个ImageButton中加属性
android:visibility="gone"
再预览就只显示menu菜单了
右侧有两个按钮,一个是调整文字大小,一个分享链接,用线性布局把两个ImageButton放里边,它两同时出现,同时隐藏
字体的图片icon_textsize.png,给它设置id为btn_text_size
分享的图片icon_share.png,给它设置id为btn_share
这两个按钮的条目默认也是隐藏的,给线性布局LinearLayout设置属性:
android:visibility="gone"
给线性布局LinearLayout加id为ll_control
NewsDetailActivity的onCreate中动态把刚才隐藏的东西都显示出来
private ImageButton btnBack;//返回按钮
private ImageButton btnMenu;//menu菜单
private LinearLayout llControl;
private ImageButton btnTextSize;//文字大小
private ImageButton btnShare;//分享
setContentView(R.layout.activity_news_detail);
btnBack = (ImageButton) findViewById(R.id.btn_back);
btnMenu = (ImageButton) findViewById(R.id.btn_menu);
llControl = (LinearLayout) findViewById(R.id.ll_control);
btnTextSize = (ImageButton) findViewById(R.id.btn_text_size);
btnShare = (ImageButton) findViewById(R.id.btn_share);
btnBack.setVisibility(View.VISIBLE);
llControl.setVisibility(View.VISIBLE);
去掉默认的黑框actionBar标题栏
在修改的红色标题栏上边有窄窄的一条黑色标题栏,在它里边显示信号,电量,时间等
onCreate的setContentView上边增加:
requestWindowFeature(Window.FEATURE_NO_TITLE);
TabDetailPager的onItemClick中增加跳到新闻详情页代码
// 跳到新闻详情页
Intent intent = new Intent();
intent.putExtra("url", news.url);// 将url参数传递到下个activity
intent.setClass(mActivity, NewsDetailActivity.class);
mActivity.startActivity(intent);
有些同学说:
1.直接new,在Intent参数中传NewsDetailActivity.class,就可以搞定
2.也可以在startActivity参数中传这些,一句话搞定
之所以用声明这么多的方式,是为后边做伏笔
运行程序,点新闻item,跳转到NewsDetailActivity页面了
Day04 11.WebView介绍和使用 ##
接下来处理新闻详情页NewsDetailActivity除了标题栏之外的下边部分
WebView的介绍
中间这个地方看着像是textView,实际它是网页,和浏览器一样是一个容器,容器中加载的是网页
这个容器叫做WebView
到activity_news_detail.xml中再写一个WebView控件,id为webview
它是android自带的比较特殊的View对象,专门用来加载网页的
NewsDetailActivity的onCreate中findViewById拿到WebView
private WebView mWebView;
mWebView = (WebView) findViewById(R.id.webview);
让WebView去加载网页一句话搞定
mWebView.loadUrl(url);
把url链接传过来给它
mWebView.loadUrl("http://www.itcast.cn");
运行程序,跳转到新闻详情页NewsDetailActivity就展示出了传智播客网页
网页太大没有适配手机端,还能左右滑,这种网页右下角有时有放大缩小按钮
给WebView加属性显示放大缩小按钮
mWebView去getsettings,拿到设置对象,要给value设置全都拿settings去搞
给settings设置如下属性
WebSettings settings = mWebView.getSettings();
settings.setBuiltInZoomControls(true);// 显示放大缩小按钮(仅限于非移动端适配的页面)
settings.setUseWideViewPort(true);// 支持双击缩放(仅限于非移动端适配的页面)
settings.setJavaScriptEnabled(true);// 支持js
setBuiltInZoomControls(true); 也支持两个手指在屏幕上滑动来放大缩小
setUseWideViewPort(true); 双击变小,再双击变大
百度(www.baidu.com)对移动端做过适配,所以无需给它设置放大缩小,设置了也无意义
使用WebView加载新闻详情页
要加载的是'网上大讲堂','马路改建'这些
在TabDetailPager的网络数据新闻news中有这些url,要把url传递给NewsDetialActivity
在TabDetailPager的lvList.setOnItemClickListener的onItemClick中
intent.putExtra("url", news.url);//将url参数传递到下个activity
NewsDetailActivity中getIntent()拿到Intent对象去getStringExtra("url"),返回一个String类型的mUrl
private String mUrl;
mUrl = getIntent().getStringExtra("url");
让webView去加载网络链接mUrl
mWebView.loadUrl(mUrl);
运行程序,点新闻中心,点网上大讲堂,清明节新闻(新闻链接假数据就设置了这重复的一条)就出来了
它一直能拉到底,要的效果是‘点击展开全文’,点击才能展开全文
这个一上来就把全文加载上来是因为这个链接的‘点击展开全文’是用js去控制的,webview默认不支持js,需要添加如下代码
settings.setJavaScriptEnabled(true);// 支持js
运行程序,‘点击展开全文’就出来了
给WebView添加进度条功能
可以给webView设置网页监听
1.mWebView.setWebViewClient(new WebViewClient() {
// 监听网页加载结束的事件
@Override
public void onPageFinished(WebView view, String url) {
mProgress.setVisibility(View.GONE);
}
});
并重写方法
1)onPageStarted 开始加载网页
2)shouldOverrideUrlLoading 网页跳转时会回调此方法
3)onPageFinished 网页加载结束
开始加载网页时可以加载一个进度条,网页加载结束可以把进度条隐藏掉
要实现这个功能,事先先埋好进度条,这个进度条是压在webView上边的
activity_news_detail.xml中给webView套一个FrameLayout,在FrameLayout中加个进度条,id为pb_loading
NewsDetailActivity的onCreate中findViewById拿到进度条
private ProgressBar pbLoading;
pbLoading = (ProgressBar) findViewById(R.id.pb_loading);
在开始加载的onPageStarted方法中显示进度条
plLoading.setVisibility(View.VISIBLE);//显示进度条
在网页加载结束的onPageFinished方法中隐藏进度条
plLoading.setVisibility(View.GONE);//隐藏进度条
相当于监听了下WebView的状态过程
运行程序,点新闻中心的'网上大讲堂',刚进入详情页时,会出来进度条,当新闻加载出来后,进度条就隐藏掉了
2)shouldOverrideUrlLoading 网页跳转时会回调此方法
什么叫网页跳转
加载网页时,网页中还会有超级链接,点超级链接网页要跳
我们这个地方没有超级链接,但如果有,你点超级链接,它要跳一个页面,点击超级链接跳转页面时会回调监听shouldOverrideUrlLoading方法
要跳的那个页面url就是这个方法参数中回传回来的url,打印一下:
System.out.println("url:" + url);
把mWebView.loadUrl(mUrl)换成mWebView.loadUrl("http://www.baidu.com");
在百度网页再点击超链接,就会在shouldOverrideUrlLoading方法中打印url
2.setWebChromeClient方法,渲染时用的是chrome内核
mWebView.setWebChromeClient(new WebChromeClient() {
});
它有如下2个方法:
onReceivedTitle 获取网页标题的回调
onProgressChanged 获取网页进度的回调
可以在onReceivedTitle中打印title值
System.out.println("title:" + title);
在onProgressChanged中打印progress值
System.out.println("progress:" + newProgress);
把上边没测试的‘打印超链接的url’,还有这里的title,progress统一测试下
运行程序,百度网页中打印的title为‘百度一下’,还打印了progress进度
网页加载时是一点一点加载,实时拿到它的进度
也可以跳链接,点百度网页中的小说就开始加载小说网页,title打印的是小说书城,进度又打印了一堆(从1%到100%),点击小说后,小说网页的url也打印为http://dushu.baidu.com
可以在小说网页再跳页面,点女生频道,它又开始加载,title是'女生频道'
这时没有打印url,难道没有跳转页面?这个我们不确定
点‘女生频道’的帝锦红颜,开始跳页面了打印url为http://m.baidu.com/tc?version=2book
打印progerss又开始加载帝锦红颜网页了,title是帝锦红颜
所以可以通过shouldOverrideUrlLoading方法和setWebChromeClient方法,把网页加载过程中的蛛丝马迹都拦截掉
WebView在实际开发中非常有用
想开发一款浏览器就可以用一个webView搞定
浏览器就是上边是EditText浏览框,一个开始加载按钮,下边是webView
在浏览框中输入想加载的内容,点击开始加载按钮,就会在webView中加载网页
有时加载网页时,还会显示进度条展示当前加载网页的进度,这个进度可以通过setWebChromeClient的onProgressChanged设置进度条
QQ浏览器之前就是用WebView去做的,但是大部分UC浏览器,chrome浏览器是自己写了一个WebView
自己写了一个网页浏览器内核,那个还是比较复杂的,知道就行
有时没必要开发一个浏览器,开发朋友圈中分享网页文章,一点进去就是一篇文章,一点就跳转到网页中了
现在就可以开发出类似于微信的这个功能
实现WebView中点击网页后跳转到activity的功能(网页和原生交互)
更牛逼的功能,项目经理让我加载一个网页,网页中有一个登陆按钮,点击后跳转到我的某个activity
一般在网页中点击某个东西后,都是网页加载网页,但这时希望网页的加载按钮能把本地的某段代码给调出来
去本地加载activity,比如微信朋友圈某个网页的‘改变的力量’按钮一点就跳转到微信的关注账号页面
等于点网页链接时跳到了本地页面
这里讲的这些api就可以实现这个功能,因为每个网络链接点击时有一个url
一般写url用一个a标签
<a href="tel:110">联系我们</a>
href中就是网络链接http://www.baidu.com,这个链接不一定必须是http
网页中有个‘联系我们’,点击后跳到打电话页面了
打电话链接是tel:110,也认为这个‘联系我们’的链接'tel:110'就是一个合法链接
点击后回传到shouldOverrideUrlLoading方法中,这个url就带过来了
打印出来的url就是'tel:110'
可以解析这个url,如果发现以'tel:'开头,就读取出电话号码,跳到打电话页面
从而实现网页和本地代码的交互,这个地方拿到链接后解析出来,写个Intent,然后startActivity就跳到打电话页面了
这时直接return一个true,表示把事件处理完了,这时这个网页肯定不加载了,网页默认加载时调的是return super.shouldOverrideUrlLoading(view, url);
super中其实在加载这个网页,直接return true就不加载这个网页了
所以通过这个链接,链接可以是tel,也可以是sms发短信,格式由你自己定
和服务器人员商量好后,确定在哪种链接时需要跳本地代码,哪种链接时需要把网页加载出来
这样就实现了一个本地代码的交互
微信网页中的按钮‘改变的力量’就是一个特殊的url,微信的webView拦截到这个url后从里边解析到‘改变的力量’的公众账号id,然后跳到公众账号的页面进行关注
把webView最核心功能都介绍到了
工作中WebView用的非常多,微信到处都是网页,因为webView能原生加载网页,网页都是动态的
服务器想什么时候更新就什么时候更新,想用什么样式随时在服务器更新样子就变了,它非常明显的特点就是实时更新
如果是android客户端,想更新特别费劲,还必须打包成apk去下载安装才能实现更新,但网页瞬时就更新了
铁道部火车站的网站也有客户端12306,可以在手机上买票
那个客户端全用webView去做的,没有用任何原生控件,全都是H5网页,H5功能要比html强大一些
你会发现它更新几乎不用去覆盖安装,它自己在那正在更新,一更新就更新了
还有一个非常好的好处,它只需要开发出一套,android和IOS就都可以用了
IOS也有类似于android的webview的控件随时加载网页
这是一个趋势,以后越来越多的客户端会使用webView做承载,一上来就是浏览器
浏览器中各种各样的标签按钮,页面跳转
目前趋势没有完全起来,客户端完全用网页打造,对网速要求非常高,稍微卡一点体验非常差
想从这个按钮切到另一个按钮,却卡半天才切换出来,但原生控件想切就切,大不了页面是个白板
网页体验还是没有原生控件那么好,很多注重用户体验的软件,微信,QQ,微博都是用原生控件打造
总在变的部分页面才会用网页
判断软件中哪些是网页哪些是原生控件的方法
设置页面,打开开发者选项,点击显示布局边界选项,这时页面会把所有控件都用蓝颜色框表示出来
打开‘智慧北京’,标题栏返回键是一个框框,中间textview没有文字所以是一个非常窄的框框
右边两个按钮是两个框框,下边空白部分中间的这个框框是progressbar
'百度一下'这个按钮没有框框,它就是这么一大块,这个就是webView,这时就确定它是webView了
之前淘宝客户端聊天页面和微信聊天一样,它是网页,网页怎么交互就怎么来,客户端不用去管,客户端去load一下url就行了,是服务器端人员去开发的,我们不需要了解那么多细节,有bug也是服务器端的bug,和客户端没关系
Day04 12.网页字体大小修改 ##
webview页面加载出来了,接下来实现标题栏上的三个按钮处理点击事件
让NewsDetailActivity实现OnClickListener
public class NewsDetailActivity extends Activity implements OnClickListener {
}
onCreate方法中分别给三个按钮设置点击监听
btnBack.setOnClickListener(this);
btnTextSize.setOnClickListener(this);
btnShare.setOnClickListener(this);
onClick中switch判断id:
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_back:
finish();
break;
case R.id.btn_text_size:
// 修改网页字体大小
showChooseDialog();
break;
case R.id.btn_share:
// 分享 ShareSDK
showShare();
break;
default:
break;
}
}
完整项目,点击修改字体大小按钮后,弹出字体设置选择对话框alertDialog
实现修改网页字体大小的功能(alertDialog的单选框)
case R.id.btn_text_size中调用showChooseDialog()方法并创建
在showChooseDialog()方法中,new个AlertDialog.Builder
AlertDialog.Builder builder = new AlertDialog.Builder(this);
用builder去setTitle为字体设置
builder.setTitle("字体设置");
用builder去设置单选效果
private int mClickItem;// 被点击选择的字体位置
builder.setSingleChoiceItems(items, mSelectedItem,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mClickItem = which;
}
});
将字体用一个String数组维护起来
String[] items = new String[] { "超大号字体", "大号字体", "正常字体", "小号字体","超小号字体" };
默认是正常字体写个2
用builder分别去设置确定取消按钮
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
}
builder.setNegativeButton("取消", null);
取消啥都不做,直接传个null就行,最后把builder去show出来
builder.show();
单选对话框setSingleChoiceItems中,用户在选时会回传到它的onClick方法中,onClick中可以拿到当前被选中的是第几个,即这个方法参数中的int which,将which保存下来
private int mClickItem;// 被点击选择的字体位置
在setSingleChoiceItems的onclick中写:
mClickItem = which;
要保留下来是因为点击‘确定’时要用到它,点确定要根据它选的是哪个位置去更新对应的字体
所以在‘确定’的setPositiveButton的onClick中再进行switch判断,看到底点的是哪个,这里判断的就是上边保存的‘被点击选择的字体位置’mClickItem
字体按钮在android原生标题栏上,要改的是webView的字体
点击某个字体,webView字体就变了
通过mWebView去getSetting拿到设置对象settings
WebSettings settings = mWebView.getSettings();
超大号字体就setTextSize,传个TextSize.LARGEST
settings.setTextSize(TextSize.LARGEST);
setTextSize方法过时了
现在用的是setTextZoom(int textZoom)传的是整数int,要具体多大字体就多大字体,比如10sp
setTextSize只有5种大小,setTextZoom字体大小更多
但用setTextZoom会报错,因为api不支持,14以上才可以用,所以在这还是用setTextSize方法
switch (mClickItem) {
case 0:
settings.setTextSize(TextSize.LARGEST);
// settings.setTextZoom(10);
break;
case 1:
settings.setTextSize(TextSize.LARGER);
break;
case 2:
settings.setTextSize(TextSize.NORMAL);
break;
case 3:
settings.setTextSize(TextSize.SMALLER);
break;
case 4:
settings.setTextSize(TextSize.SMALLEST);
break;
default:
break;
}
这样的就把网页字体大小修改了,点击确定后,不管点的是哪个字体大小就已经确定了
比如当前选的是正常字体,点确定,那当前第二个位置被选中了
再用一个变量记录被选择后的字体位置
private int mSelectedItem;// 被最终选择的字体大小的位置
一旦点击确定后,就让mSelectedItem等于当前被选中的item
mSelectedItem = mClickItem;
下次再进来就不能每次都设置第2个被选中了,应该是上次选的是谁就是谁
所以把setSingleChoiceItems的第二个参数2改为mSelectedItem
运行程序,点击标题栏上的字体大小按钮,就会出来字体设置弹框,但是怎么一上来就是超大号字体
因为一上来mSelectedItem默认是0,它默认应该是2
private int mSelectedItem = 2;// 被最终选择的字体大小的位置, 默认是正常字体大小
比如选一个超小号字体,再去点Dialog弹框时,它会根据位置去展现
有个小问题,退出把页面返回之后再进来,又重新从原来默认的第2个位置正常字体大小去展示
如果想把原来选择的字体永远持久化,就把状态保存在sp
这个地方就不保存了,就每次一进来又是正常字体,这就是字体大小的修改
Day04 13.sharesdk使用 ##
使用第三方sharesdk实现分享功能(分享WebView中的文章)
sharesdk是专门用来分享各种社会化渠道的工具,它是第三方分享的平台,支持很多种渠道,比如支持微信,QQ,朋友圈等
它是一个开放平台,可以在它上边集成很多操作
去sharesdk官网下载sdk后
ShareSDK-Android-2.5.7.tar.gz 是它下载下来的渠道
可以把它整个下载下来,它可以在官网上选择你要分享到哪些渠道,你确定好后,就可以把这个包下载下来
把ShareSDK-Android-2.5.7.tar.gz 复制到zhbj项目所在的目录文件夹下,然后解压下
这是android版的sharesdk
用firefox浏览器打开"快速集成 Mob文档中心.html"
跟着文档一点一点把sharesdk集成到项目中
1.获取AppKey
AppKey指应用要用到sharesdk什么功能,你至少要在平台上去注册一下
去注册下用户账号,邮箱,密码,注册好后zhxa项目要用到它
在上边添加应用后,sharesdk会自动分配给你AppKey作为应用的标识
用这个应用的标识可以做相应处理,sharesdk就知道是要用这个应用去分享
已经给你们生成好了,这个是黑马48期的,即 zhbj48 appkey 5701c42963ec
2.下载SDK,自定义sdk下载,
新浪微博,微信,QQ,QQ空间,腾讯微博,FaceBook,Twitter,邮件,短信分享,人人网,豆瓣,开心网,蓝牙,Pocket好多渠道
勾选要分享到哪些渠道后开始'下载SDK',它就把你选的那些渠道sdk打成包下载下来了
我已经替大家下载下来了
3.看下快速集成怎么用
1)使用快速工具进行集成,进入shareSDK解压目录把它解压,QuickIntegrater.jar就是快速集成ShareSDK的工具
windows系统下要确定安装了JDK,还要正确配置了JAVAHOME和PATH系统变量
此时就可以双击 QuickIntegrater 启动程序
把它双击启动下,会看到勾选了好多渠道,它有好多包
正常输入项目名称和包名,勾选需要的继承平台,点'确定'会自动生成目录,复制目录中所有文件到项目中覆盖即可
项目名称指的是eclipse中项目的名称zhxa02,千万不要写成中文‘智慧西安’,包名是com.itcast.zhxa02
去勾选要集成的平台,就选新浪微博,QQ,微信好友,QQ空间,微信朋友圈,印象笔记,微信收藏,WhatsApp
然后点确定,这时弹框提示:‘集成share SDK所需的文件已经复制到目录“zhxa01”中,请复制其中的文件到您的项目中覆盖’
什么意思?
再回到这个地方的时候,它就会生成zhxa02这样的目录,里边会把sharesdk需要集成的东西放进来
比如说它的资源文件assets中的ShareSDK.xml
libs文件夹下用的一些第三方的jar包
资源文件res
src目录
把生成的zhxa02复制后覆盖掉eclipse中原来的项目,就会自动去合并,覆盖之前最好先备份一份防止覆盖错了
到eclipse中zhxa02项目所在的目录下,按ctrl+v合并,合并后把zhxa02项目刷新下
它就会把sharesdk的东西添加进来,看到它添加了一个sharesdk的包,即:cn.sharesdk.onekeyshare
这是sdk的一些源码,它会把jar包也全都添加到Android Dependencies中
包括assets目录下有个ShareSDK.xml
2)合并后,要配置清单文件
(1)添加权限,把文档中的权限复制到zhxa02清单文件中
(2)添加activity信息,sharesdk自己也有一些activity,activity都要在清单文件中配置
复制文档中activity信息,粘贴到zhxa02项目清单文件中
(3)如果集成了微信或易信,还要添加下边两个Activity,我们只把微信的activity复制到zhxa02项目清单文件中
易信的我们没有集成不需要复制
zhxa02项目中的wxapi包中就是微信activity
里边有个WXEntryActivity,要分享到微信,微信平台必须要有这个activity,所以上边(3)必须要注册
(4)替换mob后台申请的AppKey与各个平台申请的key
修改成你在sharesdk后台注册的应用的appkey
我们这个appkey是5701c42963ec,修改下
<ShareSDK AppKey = "5701c42963ec"/> <!-- 修改成你在sharesdk后台注册的应用的appkey"-->
图片上还有一个箭头所指是什么呢?新浪微博对吧,因为我们这些东西,就说现在是这样的,就说sharesdk是这样的,你会发现在ShareSDK.xml中SinaWeibo有sdk,TencentWeibo也有Appkey,QZone也有Appkey
sharesdk其实是把把各大平台的分享功能整合在了一起,它本身没有分享功能
如果不用ShareSDK,自己分别去在分享的平台注册添加也可以,渠道太多时非常繁琐,所以才用ShareSDK
sharesdk已经替我们在ShareSDK.xml中把每个平台的Appkey都注册好了,就不用分别去那些平台注册了
只需要把ShareSDK的AppKey替换一下就行了,剩下的都是ShareSDK替我去注册的appkey
它只是调用别人的,要分享到微信,就要把微信的jar包,sdk乱七八糟的集成进来
你会发现libs目录下有个
微信的jar包,即:
ShareSDK-Wechat-2.5.7.jar
ShareSDK-Wechat-Core-2.5.7.jar
ShareSDK-Wechat-Favorite-2.5.7.jar
ShareSDK-Wechat-Moments-2.5.7.jar
分享QQ空间就要把QQ空间的jar包搞进来,即:ShareSDK-QZone-2.5.7.jar
3)添加分享的代码,在zhxa02项目的代码中调用文档中的这个方法,即可打开一键分享功能进行分享
把代码拷贝添加到NewsDetailActivity中,把showShare方法直接抄过来就可以了
点分享按钮时调一下这个方法就可以了,即在case R.id.btn_share:中添加:
showShare();
showShare方法的代码是什么
Notification的图标和文字,它是设置图标和文字
即:
// 分享时Notification的图标和文字
oks.setNotification(R.drawable.ic_launcher,getString(R.string.app_name));
title标题可以自己设置,即:
// title标题,印象笔记、邮箱、信息、微信、人人网和QQ空间使用
oks.setTitle(getString(R.string.share));
有时标题点击后要跳网页,这时要设置titleurl,仅在人人网和QQ空间使用
它现在默认写的是sharesdk的官网,即:
// titleUrl是标题的网络链接,仅在人人网和QQ空间使用
oks.setTitleUrl("http://sharesdk.cn");
text是分享文本,可以自己写想分享什么文字,即:
// text是分享文本,所有平台都需要这个字段
oks.setText("我是分享文本");
下边还有一个图片,在分享时可以把图片也加进去,目前这些图片是在sdcard的test.jpg
要确保SDcard下面存在此张图片才能够分享,我目前手机中没有这张图片
找张图片把它重命名成test.jpg就可以了
不需要图片可以把这个注释掉,即:
// imagePath是图片的本地路径,Linked-In以外的平台都支持此参数
// oks.setImagePath("/sdcard/test.jpg");//确保SDcard下面存在此张图片
url是网页,仅在微信朋友圈和好友中使用,即:
// url仅在微信(包括好友和朋友圈)中使用
oks.setUrl("http://sharesdk.cn");
下边的评论,是人人网和QQ空间中使用,即:
// comment是我对这条分享的评论,仅在人人网和QQ空间使用
oks.setComment("我是测试评论文本");
每个渠道它要的不一样,sharesdk干脆把他们都写进来,最后展示下,即:
// 启动分享GUI
oks.show(this);
sharesdk分享和手机卫士软件管理短信分享的区别
手机卫士只是把那段文字打开短信分享出去,只是在呼叫本地的app应用去分享
这里本地是可以不用装这个app的,可以跳网页
运行程序,点分享按钮出来个PopWindow,上边有勾选的想要的分享渠道
微信逼格比较高,不能用浏览器访问,必须本地装微信才会跳到微信页面去分享
别的都可以在本地没有app时分享
我手机装了微信的前提下,去点‘微信朋友圈’分享,来到微信朋友圈没有发出去
我这个系统现在是最新版本5.1.1,不知道这个sdk版本兼容是否有问题
点了分享新浪微博后,会跳转到一个页面,让我去输入想分享的文本
现在默认写的是我是分享文本,点分享会跳到新浪微博登陆页面,登陆后分享出去
到新浪微博App登陆新浪微博,看到已经分享成功了,标识‘刚刚 来自ShareSDK’
sharesdk自动在ShareSDK.xml中给新浪微博注册了AppKey,肯定在注册时写的来源是shareSDK
所以新浪微博一看是新浪微博的Appkey,就会显示来自于sharesdk
也可以修改ShareSDK风格,现在是默认风格,可以在NewsDetailActivity中复制过来的showShare中加 oks.setTheme(OnekeyShareTheme.SKYBLUE);//修改风格
有两种风格,CLASSIC,SKYBLUE,修改成SKYBLUE蓝天风格
运行程序,点击分享时弹出蓝色网格状风格,再点新浪微博,点击确定按钮对勾,跳转到需要你输入分享文字(感想)的页面,下边还有分享到新浪微博朋友的按钮,点确定按钮对勾就分享出去了
分享到新浪微博后将来自ShareSDK改显成来自zhxa02
刚才分享出去后显示的是来自ShareSDK,也可以显示来自zhxa02,要自己在新浪微博上注册zhxa02应用生成Appkey
到ShareSDK.xml中把SinaWeibo中的AppKey替换就行
Day04 14.总结 ##