项目重构的Git地址:https://github.com/razerdp/FriendCirclejava
下集预告:欢乐的票圈重构之旅——RecyclerView的头尾布局增长git
在沉寂了五六个月的时间后,终于有空来收拾一下朋友圈项目的残局了。 此次换了一个服务器,毕竟我们不是写后端的,当时一时头脑发热,开了一个阿里云,实际上是为了毕业设计项目着想,后来实在吃不消108软妹币每月的负担和代码的维护,因而无奈关掉服务器。 而如今,在平衡了一下LearnCloud和Bmob以后,打算采用Bmob做为咱们项目的后端支持。 因而乎,在改造的过程当中发现我们的朋友圈项目彷佛要大改,改着改着,干脆咬咬牙,所有推倒历来算了(写过的控件除外)。github
因此,羽翼君又能够来简书更新一下文章(骗赞了←_←)。编程
废话很少说了,直接进入主题。后端
此次重构由于将会从ListView换成RecyclerView,因此不少东西都要从新部署,好比上下拉。服务器
由于朋友圈的特殊性,咱们的上下拉须要符合至少两个条件:微信
对于懒惰的我来讲,首当其冲仍是找库吧。。。。结果找了一下,瞬间想哭了,由于要同时符合上面两个条件的,彷佛还真的找不到。。。有一两个比较接近的(好比:IRecyclerView)却由于各类问题致使不能使用。。。ide
没办法,只好强撸了。。 布局
因而乎,在一次提交中,狂撸Touch事件。。。 commit here动画
写着写着,想到了还得作动画,还得作返回,还得作各类各样的事件分发。。。。天呐噜,我仍是乖乖去上班吧。。。
这时候突然灵机一闪,想起之前撸ListView时不是有个overScroll的吗,那Rv也应会有的,因而面向谷歌编程的我,虽然找不到比较好的描述,但找到了这么一个库: overscroll-decor
初步看了一下代码,其核心至关于接管了touch事件,经过setTranslationY来进行View的移动的,并且最重要的是,提供的接口有着状态和偏移量的返回!!!!(拍黑板,这是重点!)
有了这两个东西,那就能够嘿嘿嘿了。
首先,咱们肯定一下咱们的控件应该怎么写。
在微信朋友圈中,以咱们的目测,至少有三个要求(本项目以iOS的交互为标准):
因此,我们的布局确定不能继承RecyclerView而后干,而是一个ViewGroup,此次我选择了FrameLayout。
因此我们的初始化这么写:
//构造器什么的,忽略啦~都指向于这里
private void init(Context context) {
//渐变背景(黑色的背景在上半部分,下半部分是白色的)
GradientDrawable background = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, new int[]{0xff323232, 0xff323232, 0xffffffff, 0xffffffff});
setBackground(background);
//rv初始化
if (recyclerView == null) {
recyclerView = new RecyclerView(context);
recyclerView.setBackgroundColor(Color.WHITE);
recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));
}
//logo初始化
if (refreshIcon == null) {
refreshIcon = new ImageView(context);
refreshIcon.setBackgroundColor(Color.TRANSPARENT);
refreshIcon.setImageResource(R.drawable.rotate_icon);
}
FrameLayout.LayoutParams iconParam = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
iconParam.leftMargin = UIHelper.dipToPx(12);
//add
addView(recyclerView, RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT);
addView(refreshIcon, iconParam);
//触发刷新的警惕线
refreshPosition = UIHelper.dipToPx(90);
//logo的观察类
iconObserver = new InnerRefreshIconObserver(refreshIcon, refreshPosition);
}
复制代码
接下来就是我们的下拉刷新了。前面说过,咱么用的是overscroll那个库,咱们针对的是偏移量,因此咱们全部的工做都依赖于这个偏移:
private void initOverScroll() {
IOverScrollDecor decor = new VerticalOverScrollBounceEffectDecorator(new RecyclerViewOverScrollDecorAdapter(recyclerView), 2f, 1f, 2f);
decor.setOverScrollUpdateListener(new IOverScrollUpdateListener() {
@Override
public void onOverScrollUpdate(IOverScrollDecor decor, int state, float offset) {
if (offset > 0) {
//正在刷新就不鸟它
if (currentStatus == REFRESHING) return;
//更新logo的位置
iconObserver.catchPullEvent(offset);
if (offset >= refreshPosition && state == STATE_BOUNCE_BACK) {
//state变成返回时,意味着已经松手了,则进行刷新逻辑
if (currentStatus != REFRESHING) {
setCurrentStatus(REFRESHING);
if (onRefreshListener != null) {
Log.i(TAG, "refresh");
onRefreshListener.onRefresh();
}
iconObserver.catchRefreshEvent();
}
}
} else if (offset < 0) {
//底部的overscroll
}
}
});
}
复制代码
代码很少,由于多的东西都在库里面干完了。。。
在调用了setAdapter以后,咱们执行这个初始化方法,从回调的接口处,不难看到offset的回调有两种,分别是大于0和小于0,其中大于0是从顶部下拉(下拉刷新),而小于0则是从底部上拉(上拉加载)。
可是,有一个问题是,咱们没有办法知道松手的触发,也就是至关于touch的up事件。不过幸亏,接口同时还返回了状态,当状态发生改变的时候,就确定是手势发生了变化,经过状态,咱们就至关于捕捉到了up事件。因此就有了以上的代码。
由于朋友圈并不须要上拉加载,而是滑动到底部自动加载更多,因此这offset<0的地方我就没有作任何逻辑了,若是有需求的话,也是能够作到上拉加载更多的。
作完上下拉的逻辑以后,接下来就是logo的联动。
从代码上来看,我把全部的逻辑都封到了iconObserver里面了(其实我以为起名叫iconHelper可能更好,但就是以为Observer高大上一点←_←)。
在observer里面,咱们主要作的东西都是跟UI有关的。代码比较简单,全部就把解释写到代码里面了
/** * 刷新Icon的动做观察者 */
private static class InnerRefreshIconObserver {
private ImageView refreshIcon;
private final int refreshPosition;
private float lastOffset = 0.0f;
private RotateAnimation rotateAnimation;
private ValueAnimator mValueAnimator;
public InnerRefreshIconObserver(ImageView refreshIcon, int refreshPosition) {
this.refreshIcon = refreshIcon;
this.refreshPosition = refreshPosition;
rotateAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.setDuration(600);
rotateAnimation.setInterpolator(new LinearInterpolator());
rotateAnimation.setRepeatCount(Animation.INFINITE);
}
public void catchPullEvent(float offset) {
if (checkHacIcon()) {
refreshIcon.setRotation(offset * 2);
if (offset >= refreshPosition) {
offset = refreshPosition;
}
int resultOffset = (int) (offset - lastOffset);
refreshIcon.offsetTopAndBottom(resultOffset);
Log.d(TAG, "pull >> " + offset + " resultOffset >>> " + resultOffset);
adjustRefreshIconPosition();
lastOffset = offset;
}
}
/** * 调整icon的位置界限 */
private void adjustRefreshIconPosition() {
if (refreshIcon.getTop() < 0) {
refreshIcon.offsetTopAndBottom(Math.abs(refreshIcon.getTop()));
} else if (refreshIcon.getTop() > refreshPosition) {
refreshIcon.offsetTopAndBottom(-(refreshIcon.getTop() - refreshPosition));
}
}
public void catchRefreshEvent() {
if (checkHacIcon()) {
refreshIcon.clearAnimation();
refreshIcon.startAnimation(rotateAnimation);
}
}
public void catchResetEvent() {
refreshIcon.clearAnimation();
if (mValueAnimator == null) {
mValueAnimator = ValueAnimator.ofFloat(refreshPosition, 0);
mValueAnimator.setInterpolator(new DecelerateInterpolator());
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float result = (float) animation.getAnimatedValue();
catchPullEvent(result);
}
});
mValueAnimator.setDuration(300);
}
mValueAnimator.start();
}
private boolean checkHacIcon() {
return refreshIcon != null;
}
}
复制代码
最后是demo动图:
本篇比较简单,算是一个开始吧,接下来的重构咱么就愉快地进行吧-V-