android实现仿QQ界面刷新

android实现仿QQ界面刷新

转载请注明出处:http://blog.csdn.net/wangpengfei_p/article/details/51420422

昨天想要实现一个下拉刷新的效果,本来想应该比较简单,因为之前在慕课网看见过类似的实现,记得是在listView里面添加footView或是添加headView,监听手指的点击滑动事件来控制view的显示或是隐藏,但是自己按照上面的代码来实现之后发现。这样做有一点不好的地方:

它判断是否刷新的依据是判断listView是不是滑动到了最后一个item。只要一旦滑动到最后一列就会自动刷新数据,我们并不能控制对数据刷新的取消。

于是我想到的QQ主界面的刷新功能:先看看QQ界面的刷新功能:

于是我就想实现这样的一个功能,但在网上搜索了好久大多的都是通过上述方式来实现的。还有一种方式是通过自定义ViewGroup来实现的(原谅我是新手,对于自定义view虽然接触了一些,仍然用不起来T_T)

今天回忆起郭霖大神的那篇博客Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑实现想了一下我是不是也可以通过动态设置view的margin来对上面的刷新条进行设置呢?

于是今天下午不断测试,不断调试,最终觉得效果有点类似QQ那样的效果了,就迫不及待的要写一篇博客了(≧▽≦)

毕竟第一篇博客,又是新手有什么不对的地方还望大家指正

先来看看最终的效果:



基本上和QQ的刷新很相似了,只是界面好篓的感觉。。。

所以来看看是怎么来实现的吧

对了里面有一些代码是借鉴自郭神的博文Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑实现有兴趣的可以看看这篇博文

先来创建一个项目,名为ListViewTest

接着在activity_main布局文件里面代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:id="@+id/load_layout"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="#FFFFFF"
        android:gravity="center"
        android:orientation="horizontal" >

        <ImageView
            android:id="@+id/main_activity_top_iv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:contentDescription="@drawable/esc"
            android:src="@drawable/esc" />

        <TextView
            android:id="@+id/main_activity_top_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下拉刷新"
            android:textSize="12sp" />
    </LinearLayout>

    <ListView
        android:id="@+id/main_activity_lv"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
    </ListView>

</LinearLayout>

这个布局文件里面主要有两部分,一个是上面的LinearLayout,用于表示刷新界面的布局,里面有一个ImageView和一个TextView用于表示刷新布局的前面的图标和后面的提示文字。这里我们让它们居中显示,因为在后面我们要对这三个控件都要进行操作,所以都要给它们加上id。

还有里面的图片是我从qq的安装包里面随便找的。

另一部分是界面的主体内容部分,我们用一个listView来进行表示,因为listView更有代表性,尤其是在存在有手指滑动的情况下。我们也给它加上id。

好了布局文件我们写好了,之后就是MainActivity里面的逻辑代码了

之后是MainActivity的内容

package com.example.listviewtest;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity implements OnTouchListener {

    // 测试的数据源
    private String[] strs = new String[30];
    private ListView listView;
    private ArrayAdapter<String> adapter;

    // 顶部刷新视图的view
    private View top;
    // 顶部刷新视图中的文字提示
    private TextView topTV;
    // 顶部刷新试图的图片提示
    private ImageView topIv;
    // 顶部刷新视图的参数,通过此参数来更改topMargin的值
    private LayoutParams topParams;

    /**
     * 顶部刷新视图的高度,通过topmargin和这个值进行比较来判断刷新视图的状态
     */
    private int topHeight = 80;

    /**
     * 当listview里面的第一个item可见的时候,初始化此值。即当刷新视图出现的时候手指所在的Y轴的位置
     */
    private int currentY;

    // 这三个值分别是手指按下,移动,和抬起的时候手指的Y轴位置
    private int moveY;
    private int downY;
    private int upY;

    /**
     * 这个值用于判断
     */
    private boolean isRead = false;

    /**
     * 这个值用于判断当前是不是正在加载数据,如果正在加载,则不对刷新视图重新进行操作,如果没有更新,则对刷新视图进行操作
     */
    private boolean isLoading = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        initDatas();
        initView();
        initValues();
    }

    /**
     * 这里使用handler来进行数据的刷新,因为过郭霖说过,在android3.0之后asyncTask变成了单队列
     * 如果两个操作想并发进行,就应该直接用Thread
     */
    private Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            new ScrollTask().execute(-10);
            Toast.makeText(getApplicationContext(), "数据刷新完成", Toast.LENGTH_SHORT).show();
        };
    };

    /**
     * 初始化各值
     */
    private void initValues() {
        topParams = (LayoutParams) top.getLayoutParams();
        topParams.height = topHeight;
        topParams.topMargin = -topHeight;
    }

    /**
     * 初始化界面控件,以及给控件添加监听事件
     */
    private void initView() {
        listView = (ListView) findViewById(R.id.main_activity_lv);
        top = findViewById(R.id.load_layout);
        topTV = (TextView) findViewById(R.id.main_activity_top_tv);
        topIv = (ImageView) findViewById(R.id.main_activity_top_iv);
        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, strs);
        listView.setAdapter(adapter);
        listView.setOnTouchListener(this);
    }

    /**
     * 初始化数据源
     */
    private void initDatas() {
        for (int i = 0; i < strs.length; i++) {
            strs[i] = String.valueOf(i);
        }
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int actionValue = event.getAction();
        int margin = 0;
        switch (actionValue) {
        case MotionEvent.ACTION_DOWN:
            downY = (int) event.getRawY();
            break;
        case MotionEvent.ACTION_MOVE:
            //如果没有加载数据则对刷新界面进行操作
            if (!isLoading) {
                moveY = (int) event.getRawY();
                if (listView.getFirstVisiblePosition() == 0 && !isRead) {
                    isRead = true;
                    currentY = moveY;
                }
                if (isRead && currentY >= downY) {
                    margin = -(topHeight - (moveY - currentY));
                    changeTop(margin);
                }
            }
            break;
        case MotionEvent.ACTION_UP:
            isRead = false;
            if (!isLoading) {
                upY = (int) event.getRawY();
                // 计算手指离开的时候刷新视图的margin值
                margin = upY - currentY - topHeight;
                if (margin < -topHeight) {
                    margin = -topHeight;
                }
                if (margin > 0) {
                    margin = 0;
                }
                // 如果手指离开的时候margin小于top布局的高度的话则就不进行刷新,否则就进行刷新
                if (Math.abs(margin) == 0) {
                    topIv.setImageResource(R.drawable.qapp_center_ico_loading);
                    topTV.setText("正在加载...");
                    Load();
                    isLoading = true;
                } else if (margin > -topHeight) {
                    new ScrollTask().execute(-10);
                    isLoading = true;
                }
                break;
            }
        }
        return false;
    }

    // 加载数据
    private void Load() {
        new Thread() {
            @Override
            public void run() {
                try {
                    // 模拟异步加载数据所消耗的时间
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                handler.sendEmptyMessage(0);
            }

        }.start();
    }

    /**
     * 根据margin的值来改变刷新视图的状态
     *
     * @param margin
     */
    private void changeTop(int margin) {
        //如果margin的值大于刷新界面的宽度的话,就把它设置为刷新界面的宽度,因为不能让整个刷新界面离开顶部
        if (margin < (-topHeight)) {
            margin = (-topHeight);
        }
        //同理,为保证整个刷新界面不离开顶部,margin也不能大于零
        if (margin > 0) {
            margin = 0;
            topTV.setText("松开进行刷新...");
            topIv.setRotation(180F);
        }
        topParams.topMargin = margin;
        top.setLayoutParams(topParams);
    }

    /**
     * 数据加载完成的时候调用,将刷新视图初始化 并将是否正在加载数据的判断属性设置为false
     */
    public void loadComplete() {
        topIv.setImageResource(R.drawable.esc);
        topIv.setRotation(0F);
        topTV.setText("下拉刷新...");
        isLoading = false;
    }

    class ScrollTask extends AsyncTask<Integer, Integer, Integer> {

        @Override
        protected Integer doInBackground(Integer... params) {
            int topMargin = topParams.topMargin;
            while (true) {
                topMargin = topMargin + params[0];
                if (topMargin > 0) {
                    topMargin = 0;
                    break;
                }
                if (topMargin < -topHeight) {
                    topMargin = -topHeight;
                    break;
                }
                publishProgress(topMargin);
                // 为了要有滚动效果产生,每次循环使线程睡眠20毫秒,这样肉眼才能够看到滚动画面
                sleep(20);
            }
            return topMargin;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            topParams.topMargin = values[0];
            top.setLayoutParams(topParams);
        }

        @Override
        protected void onPostExecute(Integer result) {
            super.onPostExecute(result);
            topParams.topMargin = result;
            top.setLayoutParams(topParams);
            loadComplete();
        }

        /**
         * 使当前线程睡眠指定的毫秒数
         *
         * @param i
         */
        private void sleep(int i) {
            try {
                Thread.sleep(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

}

里面的代码有点多,我先来说一下主体思路:

  • 将刷新界面的marginTop值设置为它的宽度的负值就能让它隐藏
  • 先初始化各个值,包括各个控件,以及给各个控件添加监听事件,以及给listView初始化数据,这些不用对说。
  • 之后的一些值有必要说明,currentY表示当滑动到顶部的时候手指所在的位置滑动过程中的值减去这个值来计算margin的值。里面的topHeight用来表示顶部的刷新界面的高度,之后的很多判断都要和这个值进行比较,isLoading这个值是用来判断现在是不是正在加载数据,如果正在加载数据,则不应该再对刷新界面进行操作。isRead这个值比较尴尬,因为这是我想到的一个解决方法,因为currentY是在监听事件的move状态中被确定的,只能初始化一次,而Touch里面的move状态只要手指一动就会调用。必须有一个值来标识currentY已经被初始化了。topParams只要用它来设置刷新界面的margin的值
  • 最后通过对手指滑动过程中的位置来计算margin的值来对刷新界面进行操作。
  • 当确定刷新的时候调用load方法进行数据的获取,数据获取完后再将刷新界面隐藏
  • 最后将隐藏的方法在一个线程里面进行,能使它缓慢隐藏(这是抄自郭霖博客(≧▽≦))

里面有几个主要注意的点:判断margin的逻辑有点混乱,还有onTouch方法一定要返回false,因为一旦返回了true的话,会导致listView无法操作,我猜测listView内部应该也实现了OnTouchListener接口,当我们设置OnTouchListener的时候在它内部会对我们传入的listener的返回值进行判断,如果为true就不对listView本身的监听事件进行操作。

到这里基本上就完成了,其中由于没有新的Activity,也没有进行什么需要权限的操作,所以manifest文件里面的代码我们也不用修改。

时间: 2024-11-08 22:49:52

android实现仿QQ界面刷新的相关文章

Android高仿QQ消息滑动删除(附源码)

大家都应该使用过QQ吧,他的消息中可以滑动删除功能,我觉得比较有意思,所以模仿写了一个,并且修改了其滑动算法.我先贴几个简单示范图吧 其实主要用的是算法以及对ListView的把控. 一下是适配器的类 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

iOS仿QQ界面

iOS仿QQ界面 仿制QQ5.0的界面,可以切换主题,并且有左右滑动特效. 下载地址:http://www.dwz.cn/z08ik 源码运行截图

分享一个Android版 仿QQ局域网即时通信软件(可发文件、语音、录音)

一.支持的功能有文字信息交互.语音聊天.发送文件和录音 源码会在后面附上. 二.UI展示图 三.经过我的测试,是非常成功的.只是有一点不足就是语音实时通话的时候声音会回声甚至死机. 文件传送和文字,录音都比较成功. 四.本软件是用Java编码,在安卓平台上的应用.使用了UDP协议和TCP协议. 大家可以学习这两部分的代码. 里面注释还是比较多. 五.当然我只是个学生,这个只是学生版本,仅供大家学习借鉴之用.绝对不能用于商业拿去直接卖,或者改改就上架某市场. 六.宣传下本人的小制作: 单机斗地主-

Android ActionBar仿微信界面

ActionBar仿微信界面 1.学习了别人的两篇关于ActionBar博客,在结合别人的文章来仿造一下微信的界面: 思路如下:1).利用ActionBar生成界面的头部,在用ActionBar的ActionProvider时候要注意引入的包一定是android.view.ActionProvider,不能是android.support.v4.view.ActionProvider 2),切换的Title可以参考之前之前一篇文章利用RadioGroup来做,这里是利用一个开源的项目PagerS

Android仿QQ界面

最近这几天,一直跟着朋友们聚会什么的,没怎么做项目,今天总算是有时间开电脑继续做我的项目了.下面我就把我做的效果展示一下. 这是模仿了qq的界面效果.因为代码比较长就不粘贴代码了.需要的小伙伴可以跟我私聊.

Android 仿QQ界面的实现

废话不说  上图  适合新手学习 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" > 很多其它功能自行下载源代码学习

仿QQ界面

作为一个新手,当然从仿开始啦,虽然实现一个对大多数人来说很简单,但是对我刚刚的新手花了两三个小时 下面是界面: 不说了,上代码更加重要 布局如下: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="mat

Android超高仿QQ附近的人搜索展示

如果我有机器猫 我要叫他小叮当 ~开车~~ 版权所有,转载请注明:http://blog.csdn.net/mr_immortalz/article/details/51319354 最近无意中发现了QQ群有查看附近的人的效果,感觉挺棒的,约炮神器有木有! 效果这么酷,网上有没有呢?木有!好吧,作为程序猿还是老老实实苦逼的撸吧. 1.概述 老规矩,先上图,再扯蛋(额,不对-) 这个就是我们撸出来的效果,原谅画质哈 (小米手机miui7不能用小米助手,所以录gif挺麻烦了) 原装货(就不录制gif

Android实现仿qq侧边栏效果

最近从github上看到一个关于侧边栏的项目,模仿的是qq侧边栏. Github地址是https://github.com/SpecialCyCi/AndroidResideMenu ,这个项目是一个android studio项目,可以导入android studio中,也可以导入到Eclipse中. 其中的ResideMenu就是写好的第三方控件,可以拿过来直接用.我们主要来看一下它是如何来运用的 public class MenuActivity extends FragmentActiv