Android自定义控件之仿汽车之家下拉刷新

关于下拉刷新的实现原理我在上篇文章Android自定义控件之仿美团下拉刷新中已经详细介绍过了,这篇文章主要介绍表盘的动画实现原理

汽车之家的下拉刷新分为三个状态:

第一个状态为下拉刷新状态(pull to refresh),在这个状态下是一个表盘随着下拉的距离动态改变指针的角度

第二个状态为放开刷新状态(release to refresh),在这个状态下是指针角度变化的一个动画

第一个状态的实现:

这个效果我们使用自定义View来实现,我们从汽车之家apk中拿到下拉刷新所用到的两张图片:

我们将第一张图片画在画布上作为背景,接着我们根据当前进度值来动态旋转画布,然后再将第二章图片画在画布上,我们看到表针的旋转实则是画布在旋转。

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
            //现将第一张图片画在画布上
            canvas.drawBitmap(finalBackGroundBitmap,0,0,null);
            //旋转画布
            canvas.rotate(mCurrentProgress*2.7f,x/2,y/2);
            //将第二张图片画在旋转后的画布上
            canvas.drawBitmap(finalPointerBitmap, 0, 0, null);
    }

自定义View的完整代码如下:

/**
 * Created by zhangqi on 15/10/17.
 */
public class AutoHome extends View{
    private Bitmap backGroundBitmap;
    public Bitmap pointerBitmap;
    private int x;
    private int y;
    private Bitmap finalBackGroundBitmap;
    private Bitmap finalPointerBitmap;
    private float mCurrentProgress;

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

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

    public AutoHome(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        backGroundBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(context.getResources(), R.drawable.load_icon_dial2x));
        pointerBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(context.getResources(), R.drawable.load_icon_pointer2x));
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec),measureWidth(heightMeasureSpec));
        x = getMeasuredWidth();
        y = getMeasuredHeight();
        finalBackGroundBitmap = Bitmap.createScaledBitmap(backGroundBitmap, x, y, false);
        finalPointerBitmap = Bitmap.createScaledBitmap(pointerBitmap, x, y, false);
    }
    private int measureWidth(int widMeasureSpec){
        int result = 0;
        int size = MeasureSpec.getSize(widMeasureSpec);
        int mode = MeasureSpec.getMode(widMeasureSpec);
        if (mode == MeasureSpec.EXACTLY){
            result = size;
        }else{
            result = backGroundBitmap.getWidth();
            if (mode == MeasureSpec.AT_MOST){
                result = Math.min(result,size);
            }
        }
        return result;

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

            canvas.drawBitmap(finalBackGroundBitmap,0,0,null);
            canvas.rotate(mCurrentProgress*2.7f,x/2,y/2);
            canvas.drawBitmap(finalPointerBitmap, 0, 0, null);
    }

    public void setCurrentProgress(float progress){
        mCurrentProgress = progress*100;
    }
}

接着我们在Activity中用SeekBar来模拟一个进度值,从而传递给我们自定义View

public class MainActivity extends AppCompatActivity {
    private SeekBar mSeekBar;
    private AutoHome mAutoHome;
    private float mCurrentProgress;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mSeekBar = (SeekBar) findViewById(R.id.seekbar);
        mAutoHome = (AutoHome) findViewById(R.id.autohome);

        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                mCurrentProgress = (float)seekBar.getProgress()/(float)seekBar.getMax();
                mAutoHome.setCurrentProgress(mCurrentProgress);
                mAutoHome.invalidate();
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
    }

第二个状态的实现:

第二个状态是表针在执行一个旋转的动画,我们可以将表针写成一个自定义View,然后表盘作为背景图片,然后表针View来执行rotate动画即可

/**
 * Created by zhangqi on 15/10/27.
 */
public class PointerView extends View {
    private int x;
    private int y;
    private Bitmap finalPointerBitmap;
    private Bitmap pointerBitmap;
    public PointerView(Context context) {
        super(context);
        init();
    }

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

    public PointerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        pointerBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.load_icon_pointer2x));
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec),measureWidth(heightMeasureSpec));
        x = getMeasuredWidth();
        y = getMeasuredHeight();
        finalPointerBitmap = Bitmap.createScaledBitmap(pointerBitmap, x, y, false);
    }
    private int measureWidth(int widMeasureSpec){
        int result = 0;
        int size = MeasureSpec.getSize(widMeasureSpec);
        int mode = MeasureSpec.getMode(widMeasureSpec);
        if (mode == MeasureSpec.EXACTLY){
            result = size;
        }else{
            result = pointerBitmap.getWidth();
            if (mode == MeasureSpec.AT_MOST){
                result = Math.min(result,size);
            }
        }
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //目的是让表针初始位置为270度!
        canvas.rotate(270,x/2,y/2);
        canvas.drawBitmap(finalPointerBitmap,0,0,null);
    }
}

然后我们在xml文件中这样写:

 <FrameLayout
    android:id="@+id/anim_container"
    android:layout_width="45dp"
    android:layout_height="45dp"
    android:layout_margin="15dp"
    android:visibility="gone" >

    <com.zhangqi.autohomerefreshlistview.PointerView
        android:id="@+id/anim_pointer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/load_icon_dial2x" />
    </FrameLayout>

这样就将表盘作为背景了,我们可以操作表针来执行rotate动画

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:toDegrees="-150"
    android:pivotY="50%"
    android:pivotX="50%"
    android:duration="1000"
    android:repeatCount="infinite"
    android:repeatMode="reverse"

    >
</rotate>
mAutoHomeAnim = (PointerView) headerView.findViewById(R.id.anim_pointer);
animation = AnimationUtils.loadAnimation(context, R.anim.pointer_rotate);
//执行动画
mAutoHomeAnim.startAnimation(animation);

在listview中

由于下拉刷新核心代码和美团下拉刷新是一样的,这里我只截取不一样的部分

private void changeHeaderByState(int state){
        switch (state) {
        case DONE:
            headerView.setPadding(0, -headerViewHeight, 0, 0);
            //第一状态的view显示出来
            mAutoHome.setVisibility(View.VISIBLE);
            //先停止一下第二阶段view的动画
            mAutoHomeAnim.clearAnimation();
            //将第二阶段view隐藏起来
            mAnimContainer.setVisibility(View.GONE);
            break;
        case RELEASE_TO_REFRESH:
            tv_pull_to_refresh.setText("放开刷新");

            break;
        case PULL_TO_REFRESH:
            tv_pull_to_refresh.setText("下拉刷新");
            //第一状态view显示出来
            mAutoHome.setVisibility(View.VISIBLE);
            //停止第二阶段动画
            mAutoHomeAnim.clearAnimation();
            //将第二阶段view隐藏
            mAnimContainer.setVisibility(View.GONE);
            break;
        case REFRESHING:
            tv_pull_to_refresh.setText("正在刷新");
            //将第一阶段view隐藏
            mAutoHome.setVisibility(View.GONE);
            //将第二阶段view显示出来
            mAnimContainer.setVisibility(View.VISIBLE);
            //先停止第二阶段动画
            mAutoHomeAnim.clearAnimation();
            //启动第二阶段动画
            mAutoHomeAnim.startAnimation(animation);
            break;
        default:
            break;
        }
    }

完整代码:

大家可以到我的GitHub上下载

版权声明:欢迎转载,转载请注明出处http://blog.csdn.net/nugongahou110

时间: 2024-10-16 04:40:21

Android自定义控件之仿汽车之家下拉刷新的相关文章

Android 轻松实现仿QQ空间下拉刷新

(本文讲解了在Android中实现列表下拉刷新的动态效果的过程,文末附有源码.) 看完本文,您可以学到: 1.下拉刷新的实现原理 2.自定义Android控件,重写其ListView 3.ScrollListener滚动监听 4.Adapter适配器的使用 话不多说,先来看看效果图: 接下来我们一步一步地实现以上的效果. 一.图文并茂的ListViewItem 看一下这一步的效果图: 首先,我们要实现的是带下拉刷新效果的ListView.所以我们选择自己重写原生控件ListView.只需要写一个

Android自定义控件(四)仿网易客户端上拉加载更多

上一篇仿得网页客户端的抽屉模式,这一篇继续,来写一写加载更多这个功能,通过自定义实现加载更多,先上图: 今天实现的就是如图中最下面的20条载入中...这个功能啦! 先来说一下思路: 1.在listview中加入20条载入中的这个布局并隐藏 2.加入OnScrollListener监听,通过监听滚动事件,当滚动到最低端的时候,显示上面的布局 3.通过接口回调实现加载更多的功能 4.加载完数据时,通知listview加载结束,隐藏上面的布局文件 下面直接上代码: 1.在listview中加入20条载

Android自定义控制(五)仿新浪微博的下拉刷新

网上有很多很有名的开源框架,这里就来拉拉PullToRefresh这个框架,也就是我们平时用的下拉刷新啦,当然你问我这个有什么用啊?别人已经写好了,这里主要是学习以及练习,练习的次数多了,一切就顺其自然的会了. 废话少说,先上图,再上代码: 1.要想实现下拉刷新功能必须要有个下拉刷新的布局,是吧? <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="htt

Android自定义控件实战——仿多看阅读平移翻页

转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38728119 之前自己做的一个APP需要用到翻页阅读,网上看过立体翻页效果,不过bug太多了还不兼容.看了一下多看阅读翻页是采用平移翻页的,于是就仿写了一个平移翻页的控件.效果如下: 在翻页时页面右边缘绘制了阴影,效果还不错.要实现这种平移翻页控件并不难,只需要定义一个布局管理页面就可以了.具体实现上有以下难点: 1.循环翻页,页面的重复利用. 2.在翻页时过滤掉多点触碰.

Android轮播图封装,下拉刷新相结合

自定义轮播图CarouselView 自定义下拉刷新PullRefreshListView 马上就要正式做毕业设计了,一些零碎时间写其中的一个模块,现记录下来,以备以后忘记时使用.欢迎大神不吝纠正. 效果图: layout_carousel.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com

Android最佳实践之Notification、下拉刷新、内存及性能建议等

Notification通知 参考地址:http://developer.android.com/training/notify-user/index.html 通知(Notification)是Android中使用的非常多的一个事件提示机制. 创建一个Notification 例子中的Notification是基于Support Library中的NotificationCompat.Builder类.我们使用时要继承这个类,它提供了各个平台最好的Notification支持. 创建一个Not

Android仿苹果版QQ下拉刷新实现(一) ——打造简单平滑的通用下拉刷新控件

前言: 因为公司人员变动原因,导致了博主四个月没有动安卓,一直在做IOS开发,如今接近年前,终于可以花一定的时间放在安卓上了.好了,废话不多说,今天我们要带来的效果是苹果版本的QQ下拉刷新.首先看一下目标效果以及demo效果:      因为此效果实现的步骤较多,所以今天博主要实现以上效果的第一步——打造一个通用的下拉刷新控件,具体效果如下: GIF图片比较大,还希望读者能耐心等待一下下从效果图中可以看出,我们的下拉刷新的滑动还是很流畅的,可能大多数开发者用的是XListview或者PullTo

Android自定义下拉刷新动画--仿百度外卖下拉刷新

好久没写博客了,小编之前一段时间一直在找工作,从天津来到了我们的大帝都,感觉还不错.好了废话不多说了,开始我们今天的主题吧.现如今的APP各式各样,同样也带来了各种需求,一个下拉刷新都能玩出花样了,前两天订饭的时候不经意间看到了"百度外卖"的下拉刷新,今天的主题就是它–自定义下拉刷新动画. 看一下实现效果吧: 动画 我们先来看看Android中的动画吧: Android中的动画分为三种: Tween动画,这一类的动画提供了旋转.平移.缩放等效果. Alpha – 淡入淡出 Scale

Android LRecyclerView 操作案例分享-实现下拉刷新、滑动到底部自动加载

一直想抽空写下这个开源项目,但是各种原因没有抽时间,今天还是趁着工作间隙写下了这篇博客,与大家分享. 简介 LRecyclerView是支持addHeaderView. addFooterView.下拉刷新.分页加载数据的RecyclerView. 它对 RecyclerView 控件进行了拓展,给RecyclerView增加HeaderView.FooterView,并且不需要对你的Adapter做任何修改. 主要功能 下拉刷新.滑动到底部自动加载下页数据: 可以方便添加Header和Foot