码迷,mamicode.com
首页 > 移动开发 > 详细

Android仿天猫下拉刷新自定义控件

时间:2016-07-13 23:19:25      阅读:403      评论:0      收藏:0      [点我收藏+]

标签:

技术分享

1、概述

控件基于android-Ultra-Pull-to-Refresh做的header定制,继承PtrFrameLayout,把事件分发给里面的RadioGroup,所以两个自定义控件分别叫HoynPtrFrameLayout,HoynRadioGroup。 因为需要PtrFrameLayout里面的一些私有属性:mPtrIndicator,mScrollChecker,所以把PtrFrameLayout代码提取出来,方便定制,使得HoynPtrFrameLayout可以更好的使用。

2、使用

布局文件分别为:layout.xml,view_header.xml,view_header_tab.xml。
layout.xml : activity布局文件
view_header.xml : RadioGroup对应的控件
view_header_tab.xml 最上面 首页、个人中心等tab控件

 hoynPtrFrameLayout.setHeaderView(view_header);  //必须
 hoynPtrFrameLayout.setTabView(tabview)          //可选

layout.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:background="@color/background"
    android:gravity="center"
    android:orientation="vertical">
<!--cube_ptr 是 android-Ultra-Pull-to-Refresh 中的属性,用法详见于android-Ultra-Pull-to-Refresh-->
    <com.hoyn.tmallpulltorefresh.HoynPtrFrameLayout xmlns:cube_ptr="http://schemas.android.com/apk/res-auto"
        android:id="@+id/store_house_ptr_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent"

        cube_ptr:ptr_duration_to_close="200"
        cube_ptr:ptr_duration_to_close_header="1000"
        cube_ptr:ptr_keep_header_when_refresh="true"
        cube_ptr:ptr_pull_to_fresh="true"
        cube_ptr:ptr_ratio_of_header_height_to_refresh="1"
        cube_ptr:ptr_resistance="1.7">
        <!-- content -->
        <LinearLayout
            android:id="@+id/store_house_ptr_image_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clickable="true"
            android:gravity="center"
            android:padding="10dp">
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_launcher" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_launcher" />
        </LinearLayout>
    </com.hoyn.tmallpulltorefresh.HoynPtrFrameLayout>
</LinearLayout>

view_header.xml

因为定制的原因,view_header只能由一个RelativeLayout里面包裹一个HoynRadioGroup构成。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/background"
    android:orientation="horizontal">
    <!--HoynRadioGroup outside layout must be RelativeLayout-->
    <com.hoyn.tmallpulltorefresh.HoynRadioGroup
        android:id="@+id/group"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:gravity="center"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/radioButton1"
            android:layout_width="0dp"
            android:layout_height="70dp"
            android:layout_weight="1"
            android:button="@null"
            android:drawableTop="@drawable/selector_share"
            android:paddingTop="17dp" />

        <RadioButton
            android:id="@+id/radioButton2"
            android:layout_width="0dp"
            android:layout_height="70dp"
            android:layout_weight="1"
            android:button="@null"
            android:checked="true"
            android:drawableTop="@drawable/selector_refresh"
            android:paddingTop="17dp" />
    </com.hoyn.tmallpulltorefresh.HoynRadioGroup>
</RelativeLayout>

MainActivity

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    private HoynPtrFrameLayout hoynPtrFrameLayout;
    private HoynRadioGroup group;
    private RadioButton btn_share, btn_refresh;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout);
        hoynPtrFrameLayout = (HoynPtrFrameLayout) findViewById(R.id.store_house_ptr_frame);
        View view_header = LayoutInflater.from(this).inflate(R.layout.view_header, null);
        View tabview = LayoutInflater.from(this).inflate(R.layout.view_header_tab, null);
        tabview.findViewById(R.id.iv_home).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "点击了首页", Toast.LENGTH_SHORT).show();
            }
        });
        group = (HoynRadioGroup) view_header.findViewById(R.id.group);
        btn_share = (RadioButton) group.findViewById(R.id.radioButton1);
        btn_refresh = (RadioButton) group.findViewById(R.id.radioButton2);
        btn_share.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "选择了分享", Toast.LENGTH_SHORT).show();
            }
        });
        btn_refresh.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "选择了刷新", Toast.LENGTH_SHORT).show();
            }
        });
        hoynPtrFrameLayout.setHeaderView(view_header);
        hoynPtrFrameLayout.setTabView(tabview);

        hoynPtrFrameLayout.setOnFiggerUpListener(new HoynPtrFrameLayout.OnFiggerUpListener() {
            @Override
            public void onFiggerUp(int checkedId) {
                if (checkedId == btn_share.getId()) {
                    //选择分享,直接到顶部
                    hoynPtrFrameLayout.scrollToTop();
                    btn_share.performClick();
                } else {
                    //选择刷新,显示loading,数据读取完,再回到顶部
                    hoynPtrFrameLayout.showProgressBar();
                    btn_refresh.performClick();
                    hoynPtrFrameLayout.showProgressBar();
                    btn_refresh.performClick();
                }
                hoynPtrFrameLayout.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        hoynPtrFrameLayout.completeRefresh();
                    }
                }, 1000);
            }
        });
    }
}

3、HoynRadioGroup源码剖析

1、构造

   private static final String TAG = "HoynRadioGroup";
    private float down_x;
    private int childCount;
    private int currentIndex;
    private Paint mPaint;
    private int mColor = 0xFFE61A5F;
    private Path path;

    private int off_left = 0, off_right = 0;
    private Circle circle;
    private List<Circle> circleList = new ArrayList<>();

    private boolean isAnimating = false; //判断动画是否正在执行
    private boolean isHeaderShow = false; //判断下拉控件是否完全显示出来且不超过1.5倍控件高度
    private boolean isShowCircle = true; //判断下拉过程中是否显示圆
    private boolean isChangeState = false;//圆动画和动画结束的切换
    private boolean isShowCircleAnimation = true; //下拉过程中是否应该显示圆的动画
    private boolean isCircleAnimating = false; //判断显示圆的动画是否在执行

    private static final int createCircleDuration = 150; //圆出现动画执行时间
    private static final int createCircleInterval = 10; //动画执行频率
    private static final int animatorDuration = 100;//圆左右移动动画执行时间
    private static final int animatorInterval = 10; //动画执行频率

    private float radius;//下拉过程中 圆动画的半径
    private float alpha;//下拉过程中 控件的透明度

    //    private View tabView;
    private int tabViewHeight = 0;

    public HoynRadioGroup(Context context) {
        super(context);
        paintInit();
    }

    public HoynRadioGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        paintInit();
    }

    //中间省略setter/getter

    private void paintInit() {
        setWillNotDraw(false);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setAlpha(100);
        mPaint.setColor(mColor);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(5);
        path = new Path();
    }

    private void animatInit() {
        isAnimating = false; //判断动画是否正在执行
        isHeaderShow = false; //判断下拉控件是否完全显示出来且不超过1.5倍控件高度
        isShowCircle = true; //判断下拉过程中是否显示圆
        isChangeState = false;//圆动画和动画结束的切换
        isShowCircleAnimation = true; //下拉过程中是否应该显示圆的动画
    }

    private boolean isShowTab = false; //用于判断是否显示header中的tabView

    public boolean isShowTab() {
        return isShowTab;
    }

    public void setIsShowTab(boolean isShowTab) {
        this.isShowTab = isShowTab;
    }

在这段代码中主要做了初始化的处理,其中setWillNotDraw(false);是一定需要的,否则后文调用invalidate();后onDraw不会执行。

2、动画

动画主要包含2个。一个是圆的显示过程,另一个是圆显示完全后,根据手势的左右滑动,圆的拉扯效果以及移动过程。

圆的显示过程:

    /**
     * create circle animation
     *
     * @param from
     * @param to
     * @param duration     animation duration
     * @param isShowCircle show or dissmiss
     * @param ev           get the down_x when animation is end;
     */
    public void circleAnimationStart(final float from, final float to, final int duration, final boolean isShowCircle, final MotionEvent ev) {
        isCircleAnimating = true;
        if (isShowCircle) {
            //let the radiobutton is checked after the circle is showed;
            //if the animation is create circle
            if (duration < 0) {
                this.isShowCircle = isShowCircle;
                isAnimating = false;
                isChangeState = isHeaderShow;
                isShowCircleAnimation = false;
                isCircleAnimating = false;
                if (ev != null)
                    down_x = ev.getX();
                return;
            }
            radius = from;
            invalidate();
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    float addInterval = to / (createCircleDuration / createCircleInterval);
                    circleAnimationStart(from + addInterval, to, duration - createCircleInterval, isShowCircle, ev);
                }
            }, createCircleInterval);
        } else {
            if (duration < 0) {
                this.isShowCircle = isShowCircle;
                isAnimating = false;
                isChangeState = isHeaderShow;
                isShowCircleAnimation = false;
                isCircleAnimating = false;
                if (ev != null)
                    down_x = ev.getX();
                return;
            }
            radius = from;
            invalidate(); //update this view
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    float addInterval = circle.getRadius() / (createCircleDuration / createCircleInterval);
                    circleAnimationStart(from - addInterval, to, duration - createCircleInterval, isShowCircle, ev);
                }
            }, createCircleInterval);
        }
    }

其中参数MotionEvent ev的作用是为了避免圆显示动画执行过程中手按下的位移变大之后出现的坐标偏差。
因为在圆的显示动画执行的过程中,按下去的坐标down_x是可能改变的,这就可能导致圆显示完了,突然一下子就拉扯的很大。

而圆的显示动画主要分为两块。
通过ShowCircle参数 来判断圆是从无到有,还是从有到无。

动画执行关键代码

postDelayed(new Runnable() {
      @Override
      public void run() {
          float addInterval = circle.getRadius() / (createCircleDuration / createCircleInterval);
          circleAnimationStart(from - addInterval, to, duration - createCircleInterval ,isShowCircle, ev);
      }
}, createCircleInterval);

addInterva是改变圆大小速度,动画用递归来执行,每次执行间隔为createCircleInterval,最后根据isShowCircle来判断from和to的关系来结束动画。

圆的移动过程:

    private int endX;
    private int mOff_left;
    private int mOff_right;
    /**
     * move animation.
     * control a circle slip to the new circle.
     *
     * @param index
     * @param onAnimatorListener
     */
    private void moveAnimationStart(final int index, OnAnimatorListener onAnimatorListener) {
        final Circle mCurrentCircle = circleList.get(index);
        int preX = circle.getX();
        int currentX = mCurrentCircle.getX();
        endX = preX;
        mOff_left = off_left;
        mOff_right = off_right;
        onAnimatorListener.onAnimatorStart();
        animationHelper(preX, currentX, animatorDuration, onAnimatorListener);
    }

    /**
     * use recursion help animator to draw the view
     *
     * @param preX               previous circle x position
     * @param currentX           current circle x position
     * @param duration           use recursion to judge the duration whether is overtime
     * @param onAnimatorListener
     */
    private void animationHelper(final int preX, final int currentX, final int duration, final OnAnimatorListener onAnimatorListener) {
        if (duration < 0) {
            return;
        }
        postDelayed(new Runnable() {
            @Override
            public void run() {
                int mPreX = preX;
                //move distance once
                int mOff_x = Math.abs(currentX - endX) / (animatorDuration / animatorInterval);
                //judge the pull is left or right
                int mPull_x = mOff_left > mOff_right ? mOff_left : mOff_right;
                //the recover time is half of the move time
                int mPull_interval = Math.abs(mPull_x) / (animatorDuration / animatorInterval) * 2;

                if (mPreX < currentX) {
                    Log.i(TAG, "toRight");
                    //update the new circle position
                    mPreX += mOff_x;
                    //gradually recover the left pull
                    if (off_right > 0) {
                        off_right -= mPull_interval;
                    } else {
                        off_right = 0;
                    }
                    if (mPreX > currentX) {
                        mPreX = currentX;
                    }
                } else {
                    Log.i(TAG, "toLeft");
                    mPreX -= mOff_x;
                    if (off_left > 0) {
                        off_left -= mPull_interval;
                    } else {
                        off_left = 0;
                    }
                    if (mPreX < currentX) {
                        mPreX = currentX;
                    }
                }
                //set circle X position to invalidate the view
                circle.setX(mPreX);
                invalidate();
                if (mPreX != currentX) {
                    animationHelper(mPreX, currentX, duration - animatorInterval, onAnimatorListener);
                } else {
                    onAnimatorListener.onAnimatorComplete();
                }
            }
        }, animatorInterval);
    }

同圆显示动画一样,通过手势,得到初始点和结束点,判断左滑还是右滑,然后计算每次移动的距离,通过递归不停的绘制圆所在的坐标。

  /**
     * the listener of animation callback
     */
    private interface OnAnimatorListener {
        void onAnimatorStart();
        void onAnimatorComplete();
    }

通过这个接口,改变radiobutton的check状态,以及记录isAnimating的状态。

    /**
     * the animation callback listener
     */
    private class AnimatorListener implements OnAnimatorListener {

        View currentChild;
        View preChild;

        public AnimatorListener(View currentChild, View preChild) {
            this.currentChild = currentChild;
            this.preChild = preChild;
        }

        @Override
        public void onAnimatorStart() {
            isAnimating = true;
        }

        @Override
        public void onAnimatorComplete() {
            isAnimating = false;
            //First set current radiobutton be checked
            ((RadioButton) currentChild).setChecked(true);
            //Next set previous radiobuton be unchecked;
            if (preChild instanceof RadioButton && currentChild != preChild) {
                ((RadioButton) preChild).setChecked(false);
            }
        }
    }

圆的拉扯效果:

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        …………
         case MotionEvent.ACTION_MOVE:
             …………
             ///pull left or right animation
             int off_x = (int) (ev.getX() - down_x);
             final View preChild = getChildAt(currentIndex);
             if (preChild == null) {
                 return super.dispatchTouchEvent(ev);
             }
             int width = preChild.getWidth();
             //Calculation of tensile strength and invalidate the view.
             if (off_x < 0 && Math.abs(off_x) < width / 2 && currentIndex != 0) {
             off_left = (int) Math.abs((off_x / 1.5));
             off_right = 0;
             } else if (off_x > 0 && Math.abs(off_x) < width / 2 && currentIndex < childCount - 1) 
             {
                 off_right = (int) Math.abs((off_x / 1.5));
                 off_left = 0;
             }
             invalidate();
             …………
         break;
    }

通过比较移动时按下的x坐标和按下时x的坐标,得到偏移量off_x,再根据它的矢量以及currentIndex的边界判断圆是否能拉动(比如index=0,就只能往右拉,不能往左拉),然后得到左边或右边拉的偏移量off_left或者off_right。
值得注意的是,在圆移动的过程中,拉扯过后的变形不是瞬间变成圆,而是慢慢恢复原状,但是恢复的速度比移动快。所以拉扯速度mPull_interval比位移速度mOff_x快一倍

int mOff_x = Math.abs(currentX - endX) / (animatorDuration / animatorInterval);
int mPull_interval = Math.abs(mPull_x) / (animatorDuration / animatorInterval) * 2;
off_right -= mPull_interval;

3、onDraw()

 @Override
    protected void onDraw(final Canvas canvas) {
        super.onDraw(canvas);
        //get the short edge
        int width = circle.getX() < circle.getY() ? circle.getX() : circle.getY();
        RectF rectLeft = null;
        RectF rectRight = null;
        if (isShowCircleAnimation) {
            //show the circle animation
            rectLeft = new RectF(width - radius, width - radius, width + radius, width + radius);
            rectRight = new RectF(width - radius, width - radius, width + radius, width + radius);
        } else {
            if (isShowCircle && isHeaderShow) {
                //show the circle and follow touch
                rectLeft = new RectF(width - circle.getRadius() - off_left, width - circle.getRadius(), width + circle.getRadius() + off_left, width + circle.getRadius());
                rectRight = new RectF(width - circle.getRadius() - off_right, width - circle.getRadius(), width + circle.getRadius() + off_right, width + circle.getRadius());
            }
        }
        if (rectLeft == null) {
            return;
        }
        rectLeft.offset(circle.getX() - circle.getRadius(), circle.getY() - circle.getRadius());
        rectRight.offset(circle.getX() - circle.getRadius(), circle.getY() - circle.getRadius());
        //draw the circle
        canvas.drawArc(rectLeft, 90, 180, true, mPaint);
        canvas.drawArc(rectRight, 270, 180, true, mPaint);
    }

onDraw()就是画圆的关键代码了。
首先判断画圆的大小。因为圆是一定在控件内部的,所以先取RadioButton的控件宽和高,取小的值赋给width。
接着判断isShowCircleAnimation,isShowCircleAnimation为true的话,需要绘制圆显示或消失,否则绘制圆的移动过程。
要实现拉扯的效果,需要绘制两个半圆,组成一个圆,然后分别控制左半圆或右半圆的宽。
new Rectf(xx,xx,xx,xx)确定矩形范围
rect.offset(xx,xx)确定矩形坐标
canvas.drawArc(rectLeft, 90, 180, true, mPaint);
画一个半圆的弧形,因为画弧形0°是从右边开始,所以从90°开始画180°的弧形,绘制出左半圆,右半圆同理。
在dispatchTouchEvent中,获取了off_left和off_right,所以只需要增加或减少矩形rect的左边宽度或右边宽度即可。

4、HoynPtrFrameLayout源码剖析

1、构造

    private static final String TAG = "PtrFrameLayout";
    //the effective max height rate,
    // if have tabview,the rate = 1 and the all height is tabview‘s height + group‘s height.
    // else the all height is group‘s height * NOT_HAS_TABVIEW_RATE;
    private float NOT_HAS_TABVIEW_RATE = 1.8f;
    private boolean hasTabView = true;
    // the header view
    private HoynRadioGroup myRadioGroup;
    // the relevant of screen
    private PtrIndicator mPtrIndicator;
    // the scroll util
    private ScrollChecker mScrollChecker;
    // draw the shadow paint
    private Paint mPaint;
    // draw the shadow transparent alpha
    private float alpha;
    // control progressBar show or hide
    private LinearLayout progressLayout;
    //custom progressBar,if null,use the default progressbar
    private ProgressBar progressBar;
    private View tabView;
    private OnFiggerUpListener onFiggerUpListener;

    private boolean isShowTab = false;

    //省略getter、setter

    public HoynPtrFrameLayout(Context context) {
        super(context);
        init();
    }

    public HoynPtrFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public HoynPtrFrameLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        //let this view can draw
        setWillNotDraw(false);
        mScrollChecker = getScrollChecker();
        mPtrIndicator = getPtrIndicator();
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLACK);
        mPaint.setAntiAlias(true);
        mPaint.setAlpha(0);
        mPaint.setStyle(Paint.Style.FILL);
    }

mScrollChecker和mPtrIndicator是PtrFrameLayout中的私有对象,把源码copy出来,写上getter方法才能获取到该对象。

2、dispatchTouchEvent传递touch事件

    @Override
    public boolean dispatchTouchEvent(MotionEvent e) {
        boolean superEvent = super.dispatchTouchEvent(e);
        //put touchEvent to customRadioGroup
        if (myRadioGroup != null) {
            myRadioGroup.dispatchTouchEvent(e);
            float headerHeight = myRadioGroup.getHeight();
            float off_y = mPtrIndicator.getCurrentPosY();
            //control the header transparent alpha.
            if (off_y < myRadioGroup.getHeight()) {
                myRadioGroup.setIsHeaderShow(false);
                //In order to Alpha change fast , so off_y/3.
                myRadioGroup.setAlpha(-0.5f + off_y / headerHeight);
            } else if (off_y > (getPtrIndicator().getHeaderHeight() + (hasTabView ? tabView.getHeight() : 0)) * (hasTabView ? 1 : NOT_HAS_TABVIEW_RATE)) {
                myRadioGroup.setIsHeaderShow(false);
                myRadioGroup.setAlpha(1);
            } else {
                myRadioGroup.setIsHeaderShow(true);
                myRadioGroup.setAlpha(1);
            }
        }
        //show the progressBar
            …………
        return superEvent;
        }

第一步就是把event传给HoynRadioGroup,这样左右滑动才能接受到事件。

第二步是添加progressbar,如果是刷新的话,就让它显示。

3、onLayout

    /**
     * add the progressBar and tabView
     */
    @Override
    protected void onLayout(boolean flag, int i, int j, int k, int l) {
        super.onLayout(flag, i, j, k, l);
        RelativeLayout headerView = (RelativeLayout) getHeaderView();
        for(int a = 0; a < headerView.getChildCount();a++){
            if(headerView.getChildAt(a) instanceof HoynRadioGroup){
                myRadioGroup = (HoynRadioGroup) headerView.getChildAt(a);
            }
        }

        if (progressLayout == null) {
            progressLayout = new LinearLayout(getContext());
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            params.height = getHeaderView().getHeight();
            progressLayout.setGravity(Gravity.CENTER);
            if (Build.VERSION.SDK_INT >= 16) {
                progressLayout.setBackground(getHeaderView().getBackground());
            } else {
                progressLayout.setBackgroundColor(Color.WHITE);
            }
            if (progressBar == null) {
                progressBar = new ProgressBar(getContext());
            }
            progressLayout.addView(progressBar);
            headerView.addView(progressLayout, params);
            progressLayout.setVisibility(INVISIBLE);
        } else {
            RelativeLayout.LayoutParams groupParams = (RelativeLayout.LayoutParams) myRadioGroup.getLayoutParams();
            int groupHeight = myRadioGroup.getHeight() + groupParams.topMargin + groupParams.bottomMargin;

            ViewGroup.LayoutParams params = progressLayout.getLayoutParams();
            if (params.height != groupHeight) {
                params.height = groupHeight;
                progressLayout.setLayoutParams(params);
            }
        }
    //add tabview
    …………
    }

由于onLayout有时会执行多次,所以在add progressBar 之前需要判断progressLayout 是否为空,如果已存在的话,更新它的宽高。
添加的时候先判断progressBar 是否为空,调用setProgressBar()可以自定义progressBar,如果没调用就为空就用系统默认的progressBar。

4、显示progressbar过程中dispatchTouchEvent的处理

    @Override
    public boolean dispatchTouchEvent(MotionEvent e) {
        boolean superEvent = super.dispatchTouchEvent(e);

        //put touchEvent to customRadioGroup
        …………
        //show the progressBar
        if (e.getAction() == MotionEvent.ACTION_DOWN) {
            hideProgressBar();
        } else if (e.getAction() == MotionEvent.ACTION_UP && onFiggerUpListener != null) {
            if (myRadioGroup.isHeaderShow()) {
                if (getFixHeader()) {
                //if tabview show complete
                …………
                } else {
                    //if is refresh
                    scrollTo(myRadioGroup.getHeight());
                    onFiggerUpListener.onFiggerUp(myRadioGroup.getCheckedRadioButtonId());
                }
            } else {
                //show the tabview
            }
        }
        return superEvent;
    }
    public interface OnFiggerUpListener {
        void onFiggerUp(int checkedId);
    }

用OnFiggerUpListener替代setPtrHandler来得到手指抬起时,当前选择RadioButton的Id。
而当手指抬起时,先调用scrollTo(myRadioGroup.getHeight());让下拉控件先滑动到RadioGroup的高度,然后在MainActivity中的hoynPtrFrameLayout.setOnFiggerUpListener判断,
如果选择的是刷新,先让progressBar显示,当数据加载完成后,再让它滑回起点。
如果是分享,直接调用hoynPtrFrameLayout.scrollToTop();到顶部。

5、TabView:

Setter:

    public void setTabView(View tabView) {
        this.tabView = tabView;
    }

添加tabView:

    /**
     * add the progressBar and tabView
     */
    @Override
    protected void onLayout(boolean flag, int i, int j, int k, int l) {
        super.onLayout(flag, i, j, k, l);
        RelativeLayout headerView = (RelativeLayout) getHeaderView();
        …………
        /**
         * add progressBar
         */
        …………
        //add tabView
        if (tabView == null) {
            /**
             * if not has tabView
             */
            hasTabView = false;
            tabView = new LinearLayout(getContext());
            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            layoutParams.height = 1;
            headerView.addView(tabView, layoutParams);
        } else {
            if (headerView.findViewById(android.R.id.text1) == null) {
                headerView.removeAllViews();
                //let the footerView  translation is 0
                //set a casual id which can let groupView below the tabView
                tabView.setAlpha(0);
                tabView.setId(android.R.id.text1);
                tabView.setTag(TAG);
                RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                headerView.addView(tabView, layoutParams);
                //RadioGroup
                RelativeLayout.LayoutParams groupParams = (RelativeLayout.LayoutParams) myRadioGroup.getLayoutParams();
                groupParams.addRule(RelativeLayout.BELOW, tabView.getId());
                myRadioGroup.setLayoutParams(groupParams);
                headerView.addView(myRadioGroup, groupParams);
                //progressbar
                int groupHeight = myRadioGroup.getHeight() + groupParams.topMargin + groupParams.bottomMargin;

                RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) progressLayout.getLayoutParams();
                params.height = groupHeight;
                params.addRule(RelativeLayout.BELOW, tabView.getId());
                progressLayout.setLayoutParams(params);
                headerView.addView(progressLayout, params);
            }
        }
    }

给tabView设置Id,android.R.id.text1是随便设置的一个,为了让该控件能找到这个View。
在第一次onLayout的时候tabView == null,因为RelativeLayout无法让一个新控件在一个顶部控件的上面,所以先removeAllViews();,添加tabView后再重新将radiogroup和progressBar加上。

tabView事件分发的处理:

    @Override
    public boolean dispatchTouchEvent(MotionEvent e) {
        boolean superEvent = super.dispatchTouchEvent(e);
        //put touchEvent to customRadioGroup
        …………
        //show the progressBar and tabView
        if (e.getAction() == MotionEvent.ACTION_DOWN) {
            hideProgressBar();
        } else if (e.getAction() == MotionEvent.ACTION_UP && onFiggerUpListener != null) {
            if (myRadioGroup.isHeaderShow()) {
                //if tabview show complete
                if (getFixHeader()) {
                    setShowTab(false);
                    scrollToTop();
                    return superEvent;
                } else {
                    //if is refresh
                    scrollTo(myRadioGroup.getHeight());
                    onFiggerUpListener.onFiggerUp(myRadioGroup.getCheckedRadioButtonId());
                }
            } else {
                if (mPtrIndicator.getCurrentPosY() > getHeaderHeight() && hasTabView) {
                    //if tabview show
                    setShowTab(true);
                    mScrollChecker.tryToScrollTo(mPtrIndicator.getHeaderHeight(), (int) getDurationToCloseHeader());
                    if (myRadioGroup.isCircleAnimating()) {
                        myRadioGroup.dismissCircleAnimationStart(e);
                    }
                    tabView.setAlpha(1);
                } else {
                    scrollToTop();
                }
            }
        }
        return superEvent;
    }

先判断getFixHeader(),为什么要判断这个呢,因为在android-Ultra-Pull-to-Refresh中,当控件是全部拉出来的时候,无论在哪里点击,都会调用默认的事件处理。RadioButton的点击事件。必须将它拦截了之后才能只调用tabview的onclick事件。

所以在PtrFrameLayout中

    //can fix the header
    private boolean fixHeader = false;

    public void setFixHeader(boolean fixHeader) {
        this.fixHeader = fixHeader;
    }

    public boolean getFixHeader(){
        return fixHeader;
    }
        @Override
    public boolean dispatchTouchEvent(MotionEvent e) {
        if (!isEnabled() || mContent == null || mHeaderView == null) {
            return dispatchTouchEventSupper(e);
        }
        int action = e.getAction();
        switch (action) {
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                /////////////////////////////////////////
                //add fixHeader
                if (fixHeader) {
                    return dispatchTouchEventSupper(e);
                }
                /////////////////////////////////////////
                mPtrIndicator.onRelease();
                if (mPtrIndicator.hasLeftStartPosition()) {
                    if (DEBUG) {
                        PtrCLog.d(LOG_TAG, "call onRelease when user release");
                    }
                    onRelease(false);
                    if (mPtrIndicator.hasMovedAfterPressedDown()) {
                        sendCancelEvent();
                        return true;
                    }
                    return dispatchTouchEventSupper(e);
                } else {
                    return dispatchTouchEventSupper(e);
                }
          }

6、下拉过程中透明度的变化:

技术分享

为了方便观察,我把上面的图又复制了一下。
从上面可以看出,在下拉过程中,header以下的部分颜色会随着拉动的off_y变深。而tabView,是在radioGroup完全拉出来之后,再继续拖动,会慢慢显示出来,直到完全显示,此时松手是让tabview显示,然后可以点击它的tab。而取消radioButton的check事件。

重写onPositionChange:

    @Override
    protected void onPositionChange(boolean isInTouching, byte status, PtrIndicator mPtrIndicator) {
        super.onPositionChange(isInTouching, status, mPtrIndicator);
        alpha = (float) mPtrIndicator.getCurrentPosY() / mPtrIndicator.getHeaderHeight();
        //the max alpha
        if (alpha > 0.8) {
            alpha = 0.8f;
        }
        //set tabview alpha
        if (hasTabView) {
            if (!isShowTab) {
                float tabAlpha = (mPtrIndicator.getCurrentPosY() - myRadioGroup.getHeight() - tabView.getHeight()) / (float) tabView.getHeight();
                // in order to set the alpha show naturally,so tabalpha / 1.5
                tabAlpha = tabAlpha / 1.5f;
                tabView.setAlpha(tabAlpha);
            } else {
                tabView.setAlpha(1);
            }
        }
        invalidate();
    }

设置0.8的原因是为了让它不全黑,保留一些透明效果。
tabalpha / 1.5 是为了让它显示的更自然,透明度不会变的那么快。导致还没开始就已经完全显示或者完全消失了。
由于alpha是全局变量,所以onDraw中可以直接用到。

onDraw():

   @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //draw the shadow and transparent alpha
        mPaint.setAlpha((int) (255 * alpha));
        canvas.drawRect(0, mPtrIndicator.getCurrentPosY(), getWidth(), getHeight(), mPaint);
        getChildAt(0).setAlpha(1 - alpha);
    }

这里就很简单了,画一个黑色背景,背景的透明度随alpha变化而变化,然后将里面的控件的透明度也让它随alpha变化。

到此就差不多结束了,完整代码已经放到github上,github会持续更新,欢迎关注。
我的GitHub

Android仿天猫下拉刷新自定义控件

标签:

原文地址:http://blog.csdn.net/adzcsx2/article/details/51899910

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!