Android动画特效第二弹——QQ聊天彩蛋蹦蹦哒

效果

在比较新的版本的手机QQ中,有许多的隐藏彩蛋。当我们发送一些特定关键字的时候,屏幕上回掉下一些到处乱蹦表情,比如输入么么哒、节日快乐这些字的时候,都会有不同的表情掉落,看上去灰常酷炫。

那么我们今天,就来简单的实现一下QQ彩蛋的效果。(效果很简单,只掉落一个表情,各位大神如果想要扩展的话 可以自己添加)效果图如下:

从上图中我们可以看到, 到我们输入特定关键字“me”的时候,屏幕上回掉下亲亲的表情;输入“ku”的时候,会掉下哭的表情。并且表情是从屏幕的最上方开始掉落,掉落到第一个对话框后,弹了几下,然后掉落到下一个对话框,直到落到最后一个对话框后消失。

**

知识点

**

本文中涉及到的主要知识点有:

(一)ListView加载不同布局

(二)属性动画的使用

(三)使用反射来获取状态栏的高度

分析

首先我们需要做出我们的聊天界面的布局,总体来说上面是一个ListView,根据消息的来源(发出或接受)加载不同的布局。最下面是一个输入框和一个按钮。

先看主界面activity_main.xml的布局

activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:background="@drawable/chat_bg_default"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
<ImageView
    android:id="@+id/emoji"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:visibility="invisible"
    />
    <RelativeLayout
        android:id="@+id/id_ly_top"
        android:layout_width="fill_parent"
        android:layout_height="45dp"
        android:layout_alignParentTop="true"
        android:background="@drawable/title_bar" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="WeChat"
            android:textColor="#ffffff"
            android:textSize="22sp" />
    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/id_ly_bottom"
        android:layout_width="fill_parent"
        android:layout_height="55dp"
        android:layout_alignParentBottom="true"
        android:background="@drawable/bottom_bar" >

        <Button
            android:id="@+id/id_send_msg"
            android:layout_width="60dp"
            android:layout_height="40dp"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:background="@drawable/send_btn_bg"
            android:text="发送" />

        <EditText
            android:id="@+id/id_input_msg"
            android:layout_width="fill_parent"
            android:layout_height="40dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_toLeftOf="@id/id_send_msg"
            android:background="@drawable/login_edit_normal"
            android:textSize="18sp" />
    </RelativeLayout>

    <ListView
        android:id="@+id/id_listview_msgs"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_above="@id/id_ly_bottom"
        android:layout_below="@id/id_ly_top"
        android:divider="@null"
        android:dividerHeight="5dp" >
    </ListView>

</RelativeLayout>

布局中的ImageView就是我们要掉落的表情,这里简单起见只用了一个ImageView,如果想实现更加华丽动态的效果,小伙伴们可以使用自定义View.其他的就是ListView、下面的输入框和发送按钮,没什么好多说的。

然后,我们需要编写一个实体类ChatMessage来表示我们的聊天消息。

public class ChatMessage
{

    private String name; //发送人的名字
    private String msg;//发送的消息
    private Type type;//消息的类型  接受,发送
    private Date date;//发送的时间

    public enum Type
    {
        INCOMING, OUTCOMING
    }

    public ChatMessage()
    {
    }

    public ChatMessage(String msg, Type type, Date date)
    {
        super();
        this.msg = msg;
        this.type = type;
        this.date = date;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public String getMsg()
    {
        return msg;
    }

    public void setMsg(String msg)
    {
        this.msg = msg;
    }

    public Type getType()
    {
        return type;
    }

    public void setType(Type type)
    {
        this.type = type;
    }

    public Date getDate()
    {
        return date;
    }
    public void setDate(Date date)
    {
        this.date = date;
    }

}

然后是我们的最重要的MainActivity中的代码了。有点复杂,需要层层解剖。

MainActivity.class

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化View
        initView();
        //初始化数据
        initData();
        //绑定事件
        initEvent();

    }
    /**
     * 初始化View
     */
    private void initView()
    {
        mListView = (ListView) findViewById(R.id.id_listview_msgs);
        mInputMsg = (EditText) findViewById(R.id.id_input_msg);
        mSendMsg = (Button) findViewById(R.id.id_send_msg);
        mEmoji = (ImageView) findViewById(R.id.emoji);

        // 将表情移到屏幕外面
        resetEmoji();

    }
    /**
     * 初始化数据
     */
    private void initData()
    {
        mLists = new ArrayList<ChatMessage>();
        mLists.add(new ChatMessage("你好!", Type.INCOMING, new Date()));
        mAdapter = new ChatAdapter();
        mListView.setAdapter(mAdapter);
    }

    private void initEvent()
    {
        mSendMsg.setOnClickListener(new OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                final String toMsg = mInputMsg.getText().toString();
                if (TextUtils.isEmpty(toMsg))
                {
                    Toast.makeText(MainActivity.this, "发送消息不能为空!",
                            Toast.LENGTH_SHORT).show();
                    return;
                }
                // 发送消息
                ChatMessage toMessage = new ChatMessage();
                toMessage.setDate(new Date());
                toMessage.setMsg(toMsg);
                toMessage.setType(Type.OUTCOMING);
                mLists.add(toMessage);
                mAdapter.notifyDataSetChanged();
                // 让ListView列表始终显示最后一条记录
                mListView.setSelection(mLists.size() - 1);
                mInputMsg.setText("");
                Message m = Message.obtain();
                m.obj = toMessage;
                mHandler.sendMessageDelayed(m, 500);

            }

        });
    }

initView和initData方法主要是绑定控件和初始化数据。

/**
     * 聊天View的adapter
     *
     * @author Jacques 2015-5-19
     */
    class ChatAdapter extends BaseAdapter
    {

        @Override
        public int getCount()
        {

            return mLists.size();
        }

        @Override
        public Object getItem(int position)
        {

            return mLists.get(position);
        }

        @Override
        public long getItemId(int position)
        {

            return position;
        }

        @Override
        public int getItemViewType(int position)
        {
            ChatMessage chatMessage = mLists.get(position);
            if (chatMessage.getType() == Type.INCOMING)
            {
                return 0;
            }
            return 1;
        }

        @Override
        public int getViewTypeCount()
        {
            return Type.values().length;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent)
        {

            ChatMessage chatMessage = mLists.get(position);
            ViewHolder viewHolder = null;
            if (convertView == null)
            {
                // 通过ItemType设置不同的布局
                if (getItemViewType(position) == 0)
                {
                    convertView = getLayoutInflater().inflate(
                            R.layout.item_from_msg, parent, false);
                    viewHolder = new ViewHolder();
                    viewHolder.mDate = (TextView) convertView
                            .findViewById(R.id.id_msg_date);
                    viewHolder.mMsg = (TextView) convertView
                            .findViewById(R.id.id_msg_info);
                } else
                {
                    convertView = getLayoutInflater().inflate(
                            R.layout.item_to_msg, parent, false);
                    viewHolder = new ViewHolder();
                    viewHolder.mDate = (TextView) convertView
                            .findViewById(R.id.id_msg_date);
                    viewHolder.mMsg = (TextView) convertView
                            .findViewById(R.id.id_msg_info);
                }
                convertView.setTag(viewHolder);
            } else
            {
                viewHolder = (ViewHolder) convertView.getTag();
            }
            // 设置数据
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            viewHolder.mDate.setText(df.format(chatMessage.getDate()));
            viewHolder.mMsg.setText(chatMessage.getMsg());
            return convertView;
        }

    }

    private final class ViewHolder
    {
        TextView mDate;
        TextView mMsg;
    }

在adapter中,我们重写了getItemViewType和getViewTypeCount两个方法,来实现根据消息类型加载不同的布局。

在initEvent方法中,处理发送按钮的点击事件,我们将发送的消息交给handler来处理。

private Handler mHandler = new Handler()
    {
        public void handleMessage(android.os.Message msg)
        {
            // 等待接收,子线程完成数据的返回
            final ChatMessage fromMessge = (ChatMessage) msg.obj;
            final int[] location = new int[2];
            final List<int[]> position = new ArrayList<int[]>();
            mListView.post(new Runnable()
            {

                @Override
                public void run()
                {
                    int first = mListView.getFirstVisiblePosition();
                    int last = first + mListView.getChildCount() - 1;

                    for (int i = first; i <= last; i++)
                    {
                        final View view = getViewByPosition(i, mListView);
                        TextView tx = (TextView) view
                                .findViewById(R.id.id_msg_info);
                        // 获取聊天消息的TextView在屏幕中的坐标
                        tx.getLocationInWindow(location);
                        int[] locationWithStatusBar = { 0, 0 };
                        locationWithStatusBar[0] = location[0];
                        // 去掉顶部的状态栏的高度
                        locationWithStatusBar[1] = location[1]
                                - getStatusBarHeight();
                        if (mAdapter.getItemViewType(i) == 1)
                        {
                            position.add(locationWithStatusBar);
                        }
                    }
                    /**
                     * 跳出彩蛋表情
                     */
                    jumpEmoji(fromMessge.getMsg(), position);
                }

            });

        };

    };

在handler中,我们接受消息,并且通过ListView的post方法,在ListView加载完成数据后, 获取所有右边的输入框在屏幕中的坐标,存到一个集合position 中。

/**
     * 彩蛋表情跳跃动画
     *
     * @param toMsg
     * @param position
     */
    private void jumpEmoji(String toMsg, List<int[]> position)
    {
        mEmoji.setVisibility(View.VISIBLE);
        mEmoji.bringToFront();
        /**
         * 匹配表情
         */
        if (toMsg.contains("me"))
        {
            startJump(position);
            mEmoji.setImageResource(R.drawable.qin);
        } else if (toMsg.contains("ku"))
        {
            startJump(position);

            mEmoji.setImageResource(R.drawable.ku);
        }

    }

接下来,执行jumpEmoji方法。jumpEmoji方法根据发送的消息来匹配应该掉落的表情。比如消息中包含“me”,就掉落亲亲的表情;包含“ku”就掉落哭的表情。这里只是做了简单的匹配以做演示。

/**
     * 开始跳跃动画
     * @param position
     */
    private void startJump(List<int[]> position)
    {
        // 开始动画效果
                AnimatorSet animatorSets = new AnimatorSet();
                List<Animator> animators = new ArrayList<Animator>();

                for (int i = 0; i < position.size(); i++)
                {
                    PropertyValuesHolder transX;
                    PropertyValuesHolder transY;
                    int[] po = position.get(i);
                    Log.v("MainActivity", po[0] + ":" + po[1]);
                    if (i == 0)
                    {
                        transX = PropertyValuesHolder.ofFloat("translationX", po[0],
                                po[0]);
                        transY = PropertyValuesHolder.ofFloat("translationY", -30f,
                                po[1]);

                    } else
                    {
                        int[] prePo = position.get(i - 1);
                        transX = PropertyValuesHolder.ofFloat("translationX", po[0],
                                po[0]);
                        transY = PropertyValuesHolder.ofFloat("translationY", prePo[1],
                                po[1]);

                    }

                    ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
                            mEmoji, transX, transY);
                    animator.setInterpolator(new BounceInterpolator());
                    animator.setDuration(1500);
                    animator.setStartDelay(200);
                    animators.add(animator);
                }
                animatorSets.playSequentially(animators);
                animatorSets.start();
                animatorSets.addListener(new AnimatorListener()
                {

                    @Override
                    public void onAnimationStart(Animator animation)
                    {
                    }

                    @Override
                    public void onAnimationRepeat(Animator animation)
                    {
                    }

                    @Override
                    public void onAnimationEnd(Animator animation)
                    {
                        // 让动画表情复位
                        resetEmoji();
                        mEmoji.clearAnimation();

                    }

                    @Override
                    public void onAnimationCancel(Animator animation)
                    {
                    }
                });
                animatorSets = null;
    }

最后,startJump方法是真正执行动画的方法。在这里,我们使用属性动画来完成一系列动画的操作。

前面分析过,动画是从屏幕最上边开始掉落,调到第一个聊天框后弹跳几下,然后调到第二个聊天框,直到掉落到最后一个聊天框后消失。

在for循环中,我们分别处理emoji表情在每个对话框处X和Y两个方向的位移动画,并且使用BounceInterpolator弹性插值器来产生掉落后的弹跳效果。

private void resetEmoji()
    {
        AnimatorSet set = new AnimatorSet();
        ObjectAnimator animatorX = new ObjectAnimator();
        ObjectAnimator animatorY = new ObjectAnimator();
        animatorX = ObjectAnimator.ofFloat(mEmoji, "translationX", 0f);
        animatorY = ObjectAnimator.ofFloat(mEmoji, "translationY", -30f);
        set.playTogether(animatorX, animatorY);
        mEmoji.setVisibility(View.INVISIBLE);
        set.start();
    }

在emoji表情初始化,以及每次动画结束的时候,我们都需要调用resetEmoji方法来使Image回到原先的位置。

**注意:**getLocationInWindow方法获取到的坐标的高度是包含状态栏(显示电量和WIFI信号的那一栏)和标题栏的,所以我们需要去掉标题栏和状态栏的高度。对于状态栏的高度,在很多情况下获取到的都是0,一种有效的方法是使用反射来获取。

/**
     * 通过反射获取状态栏的高度
     *
     * @return
     */
    private int getStatusBarHeight()
    {
        Class<?> c = null;
        Object obj = null;
        Field field = null;
        int x = 0, sbar = 0;
        try
        {

            c = Class.forName("com.android.internal.R$dimen");
            obj = c.newInstance();
            field = c.getField("status_bar_height");
            x = Integer.parseInt(field.get(obj).toString());
            sbar = getResources().getDimensionPixelSize(x);

        } catch (Exception e1)
        {
            e1.printStackTrace();
        }

        return sbar;
    }
时间: 2024-10-06 15:34:33

Android动画特效第二弹——QQ聊天彩蛋蹦蹦哒的相关文章

Android 之文件存储(文末有彩蛋)

1. I/O流分为 字节流 和 字符流. 字节流:InputStream.OutputStream(输入流.输出流) 字符流:Reader.Writer(输入流.输出流) 注:1 字符 = 2 字节 缓冲流:BufferedReader.BufferedWriter(缓冲输入.输出流) 2. 打开数据库:sqlite3: 3. 修改文件权限:chomd   例:chomd+ 777 +文件名 4. Android SQLite 判断数据库中的文件为空 //查询数据 public void que

Android 动画特效

一.渐变动画 AlphaAnimation aa = new AlphaAnimation(0.3f, 1.0f); // fromAlpha , toAlpha aa.setDuration(2000); //持续时间 view.startAnimation(aa); //为目标组件绑定动画效果 aa.setAnimationListener(new AnimationListener() { @Override public void onAnimationEnd(Animation arg

Android群英传笔记——第七章:Android动画机制和使用技巧

Android群英传笔记--第七章:Android动画机制和使用技巧 想来,最近忙的不可开交,都把看书给冷落了,还有好几本没有看完呢,速度得加快了 今天看了第七章,Android动画效果一直是人家中十分重要的一部分,从早期的Android版本中,由于动画机制和绘图机制的不健全,Android的人机交互备受诟病,Android从4.X开始,特别是5.X,动画越来越完善了,Google也开始重视这一方面了,我们本章学习的主要内容有 Android视图动画' Android属性动画 Android动画

Android群英传知识点回顾——第七章:Android动画机制与使用技巧

7.1 Android View动画框架 7.1.1 透明度动画 7.1.2 旋转动画 7.1.3 位移动画 7.1.4 缩放动画 7.1.5 动画集合 7.2 Android属性动画分析 7.2.1 ObjectAnimator 7.2.2 PropertyValuesHolder 7.2.3 ValueAnimator 7.2.4 动画事件的监听 7.2.5 AnimatorSet 7.2.6 在XML中使用属性动画 7.2.7 View的animate方法 7.3 Android布局动画

Android彩蛋效果,微信彩蛋效果

根据Android源码修改,具有微信彩蛋效果 主要代码 public static class Board extends FrameLayout { public static final boolean FIXED_STARS = true; // 控制数量 public static final int NUM_CATS = 30; static Random sRNG = new Random(); static float lerp(float a, float b, float f)

Android特效专辑(六)——仿QQ聊天撒花特效,无形装逼,最为致命

Android特效专辑(六)--仿QQ聊天撒花特效,无形装逼,最为致命 我的关于特效的专辑已经在CSDN上申请了一个专栏--http://blog.csdn.net/column/details/liuguilin.html 日后我所写的特效专辑也会以一添加在这个专栏上,今天写的这个特效,是关于聊天的,你肯定遇到过,就是你跟人家聊天的时候,比如发送应(么么哒),然后屏幕上全部就是表情了,今天我们就是做这个,撒花的特效,国际惯例,上图 截图 实现这样的效果,你要知道贝塞尔曲线,何谓贝塞尔曲线?其实

Android,iOS打开手机QQ与指定用户聊天界面

在浏览器中可以通过JS代码打开QQ并弹出聊天界面,一般作为客服QQ使用.而在移动端腾讯貌似没有公布提供类似API,但是却可以使用schema模式来启动手机QQ. 以下为具体代码: Android: String url="mqqwpa://im/chat?chat_type=wpa&uin=123456"; startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); iOS: UIWebView *webView

Android Animation动画详解(二): 组合动画特效

前言 上一篇博客Android Animation动画详解(一): 补间动画 我已经为大家介绍了Android补间动画的四种形式,相信读过该博客的兄弟们一起都了解了.如果你还不了解,那点链接过去研读一番,然后再过来跟着我一起学习如何把简单的动画效果组合在一起,做出比较酷炫的动画特效吧. 一. 动画的续播 如题,大家想想,如果一个页面上包含了许多动画,这些动画要求按顺序播放,即一个动画播放完成后,继续播放另一个动画,使得这些动画具有连贯性.那该如何实现呢? 有开发经验或者是逻辑思维的人肯定会想,对

Android的Activity切换动画特效库SwitchLayout,视图切换动画库,媲美IOS

由于看了IOS上面很多开发者开发的APP的视图界面切换动画体验非常好,这些都是IOS自带的,但是Android的Activity等视图切换动画并没有提供原生的,所以特此写了一个可以媲美IOS视图切换动画的Android视图切换动画特效库!SwitchLayout!可以说是目前Android上第一个,也是唯一的一个强大的视图切换动画库引擎! 作者:谭东 QQ:852041173 项目开源!推荐使用jar包形式! 没有经过作者允许,不可修改项目库源码自行发布. 如果你的项目中使用了SwtichLay