上一篇菊花loading可以根据项目的的UI设计要求设置自己需要的样式,现在再贴一 段color[]代码,
private String[] color = {"#aaaaaa", "#999999", "#888888", "#777777", "#666666", "#bbbbbb"};
这个颜色数组可以可以很好的模仿出ios的菊花颜色,然后是菊花矩形圆角问题, 可以使用canvas.drawRoundRect( RectF rect, float rx, float ry, Paint paint),这样就可以完美的解决问题了。本来老铁说上一篇菊花loading会火的,因为网上没有写过类似的文章,可惜到现在也没火。哎,废话不多了,上一篇说过要给大家一个全新的弹性容器,不过在此之前我还是要想说一句,view的事件分发机制,大家一定要弄清楚,这样在阅读下面代码的时候会事半功倍。
效果图:
实现原理:定义一个外层容器,内层可以是recyclerview,Listview,ScrollView等可以滑动的组件,主要是在事件分发上动手脚,在事件分发dispatchTouchEvent中根据内层recyclerview中可见的item是否处于顶部或者底部来处理事件分发(自己消费掉或者将事件交由当前view的onlnterceptTouchEvent进行事件拦截,而此处事件不拦截,交给recycelrview进行消费),还是那句话把事件分发搞明白,具体查看具有回弹效果的RecyclerView,RecyclerView外层可滚动容器,不会的一定要搞明白了,哈哈,我实诚吧。
还有下面的弹性是通过onlayout来实现的,和上一篇的弹性容器不一样,大家可以参考上一篇进行修改,重要的点会在下面代码中给大家进行讲解
/**
* Created by Sick on 2016/12/1.
*/
public class PullRecyclerViewGroup extends LinearLayout implements ViewTreeObserver.OnGlobalLayoutListener {
/**
* 滚动时间
*/
private static final long ANIM_TIME = 400;
//listview 或者recyclerview或者ScrollView
private View childView;
// 用于记录正常的布局位置
private Rect originalRect = new Rect();
//滚动时,移动的view和位置
private List mMoveViews = new ArrayList();
private List mMoveRects = new ArrayList();
// 在手指滑动的过程中记录是否移动了布局
private boolean isMoved = false;
// 如果按下时不能上拉和下拉, 会在手指移动时更新为当前手指的Y值
private float startY;
//阻尼
private static final float OFFSET_RADIO = 0.5f;
private boolean isRecyclerReuslt = false;
public PullRecyclerViewGroup(Context context) {
this(context, null);
}
public PullRecyclerViewGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PullRecyclerViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//关闭右侧滚动条
this.setVerticalScrollBarEnabled(false);
}
/**
* 加载布局后初始化,这个方法会在加载完布局后调用
*/
@Override
protected void onFinishInflate() {
//此处为容器中的子view 必须有RecyclerView、ListView、ScrollView,当然这里忽略ListView和ScrollView
if (getChildCount() > 0) {
for (int i = 0; i < getChildCount(); i++) {
if (getChildAt(i) instanceof RecyclerView || getChildAt(i) instanceof ListView || getChildAt(i) instanceof ScrollView) {
if (childView == null) {
childView = getChildAt(i);
} else {
throw new RuntimeException("PullRecyclerViewGroup 中只能存在一个RecyclerView、ListView或者ScrollView");
}
}
}
}
if (childView == null) {
throw new RuntimeException("PullRecyclerViewGroup 子容器中必须有一个RecyclerView、ListView或者ScrollView");
}
//布局重绘监听,比如华为屏幕键盘可以弹出和隐藏,改变布局,加监听就可以虽键盘弹出关闭的变化而变化
getViewTreeObserver().addOnGlobalLayoutListener(this);
super.onFinishInflate();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
//ScrollView中唯一的子控件的位置信息,这个位置在整个控件的生命周期中保持不变
originalRect.set(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom());
for (int i = 0; i < mMoveViews.size(); i++) {
final View v = mMoveViews.get(i);
v.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
Rect rect = new Rect();
rect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
mMoveRects.add(rect);
v.removeOnLayoutChangeListener(this);
}
});
}
}
/**
* 事件分发
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (childView == null) {
return super.dispatchTouchEvent(ev);
}
boolean isTouchOutOfScrollView = ev.getY() >= originalRect.bottom || ev.getY() <= originalRect.top; //如果当前view的Y上的位置
if (isTouchOutOfScrollView) {//如果不在view的范围内
if (isMoved) { //当前容器已经被移动
recoverLayout();
}
return true;
}
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//记录按下时的Y
startY = ev.getY();
case MotionEvent.ACTION_MOVE:
float nowY = ev.getY();
int scrollY = (int) (nowY - startY);
if ((isCanPullDown() && scrollY > 0) || (isCanPullUp() && scrollY < 0) || (isCanPullDown() && isCanPullUp())) {
int offset = (int) (scrollY * OFFSET_RADIO);
childView.layout(originalRect.left, originalRect.top + offset, originalRect.right, originalRect.bottom + offset);
for (int i = 0; i < mMoveViews.size(); i++) {
if (mMoveViews.get(i) != null) {
mMoveViews.get(i).layout(mMoveRects.get(i).left, mMoveRects.get(i).top + offset, mMoveRects.get(i).right, mMoveRects.get(i).bottom + offset);
}
}
isMoved = true;
isRecyclerReuslt = false;
return true;
} else {
startY = ev.getY();
isMoved = false;
isRecyclerReuslt = true;
recoverLayout();
return super.dispatchTouchEvent(ev);
}
case MotionEvent.ACTION_UP:
if (isMoved) {
recoverLayout();
}
if (isRecyclerReuslt) {
return super.dispatchTouchEvent(ev);
} else {
return true;
}
default:
return true;
}
}
/**
* 位置还原
*/
private void recoverLayout() {
if (!isMoved) {
return;//如果没有移动布局,则跳过执行
}
for (int i = 0; i < mMoveViews.size(); i++) {
if (mMoveRects.get(i) != null) {
TranslateAnimation anims = new TranslateAnimation(0, 0, mMoveViews.get(i).getTop(), mMoveRects.get(i).top);
anims.setDuration(ANIM_TIME);
mMoveViews.get(i).startAnimation(anims);
mMoveViews.get(i).layout(mMoveRects.get(i).left, mMoveRects.get(i).top, mMoveRects.get(i).right, mMoveRects.get(i).bottom);
}
}
TranslateAnimation anim = new TranslateAnimation(0, 0, childView.getTop() - originalRect.top, 0);
anim.setDuration(ANIM_TIME);
childView.startAnimation(anim);
childView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom);
isMoved = false;
}
/**
* 容器的的事件都在事件分发中处理,这里处理的是事件分发传递过来的事件,
*
* 传递过来的为RecyclerVIew的事件 不拦截,直接交给reyclerview处理
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false; //不拦截 直接传递给子的view
}
/**
* 判断是否可以下拉
* @return
*/
private boolean isCanPullDown() {
final RecyclerView.Adapter adapter = ((RecyclerView) childView).getAdapter();
if (null == adapter) {
return true;
}
//spf ========================================================
final int firstVisiblePosition = ((LinearLayoutManager) ((RecyclerView) childView).getLayoutManager()).findFirstVisibleItemPosition();
if (firstVisiblePosition != 0 && adapter.getItemCount() != 0) {
return false;
}
//===================================================================================
int mostTop = (((RecyclerView) childView).getChildCount() > 0) ? ((RecyclerView) childView).getChildAt(0).getTop() : 0;
return mostTop >= 0;
}
/**
* 判断是否可以上拉
* @return
*/
private boolean isCanPullUp() {
final RecyclerView.Adapter adapter = ((RecyclerView) childView).getAdapter();
if (null == adapter) {
return true;
}
final int lastItemPosition = adapter.getItemCount() - 1;
final int lastVisiblePosition = ((LinearLayoutManager) ((RecyclerView) childView).getLayoutManager()).findLastVisibleItemPosition();
if (lastVisiblePosition >= lastItemPosition) {
final int childIndex = lastVisiblePosition - ((LinearLayoutManager) ((RecyclerView) childView).getLayoutManager()).findFirstVisibleItemPosition();
final int childCount = ((RecyclerView) childView).getChildCount();
final int index = Math.min(childIndex, childCount - 1);
final View lastVisibleChild = ((RecyclerView) childView).getChildAt(index);
if (lastVisibleChild != null) {
return lastVisibleChild.getBottom() <= childView.getBottom() - childView.getTop();
}
}
return false;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onGlobalLayout() {
//华为手机屏幕下方的返回、home键显示隐藏改变布局
requestLayout();
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
/**
* 跟随弹性移动的view
* @param view
*/
public void setMoveViews(View view) {
this.mMoveViews.add(view);
requestLayout();
}
}
上面就是核心的代码了,调用记得给标题栏设置随着view的移动而移动,即setMoveViews()