Android动画特效第二弹——QQ聊天彩蛋蹦蹦哒
2015-05-19 23:57
603 查看
效果
在比较新的版本的手机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; }
相关文章推荐
- Android动画特效第二弹——QQ聊天彩蛋蹦蹦哒
- IOS开发-UIDynamic(物理仿真)模拟QQ聊天界面的特效图片动画
- Android仿QQ聊天撒花特效 很真实
- Android特效专辑(六)——仿QQ聊天撒花特效,无形装逼,最为致命
- Android QQ空间浏览图片动画特效的实现(※)
- Android特效专辑(六)——仿QQ聊天撒花特效,无形装逼,最为致命
- (转)Android实用视图动画及工具系列之六:通用表情栏,仿QQ微信聊天弹出表情选框
- Android——仿QQ聊天撒花特效
- Android特效专辑(六)——仿QQ聊天撒花特效,无形装逼,最为致命
- Android QQ空间浏览图片动画特效的实现(※)
- Android实用视图动画及工具系列之六:通用表情栏,仿QQ微信聊天弹出表情选框
- Android仿QQ聊天撒花特效
- Android实现Activity页面跳转切换动画特效
- Android NDK——App端通过串口通信完成实时控制单片机上LED灯的颜色及灯光动画特效
- AndroidRichText 让Textview轻松的支持富文本(图像ImageSpan、点击效果等等类似QQ微信聊天)
- Android 仿qq聊天界面之一
- Android UI【android 仿微信、QQ聊天,带表情,可翻页,带翻页拖动缓冲】
- 【android】软键盘 - 仿 qq/微信 聊天界面布局
- [置顶] Android ListView动画特效实现原理及源码
- android组件化方案、二维码扫码、Kotlin新闻客户端、动画特效等源码