【Android自定义View】 仿照腾讯漫画自定义Toast的实现
效果展示:
话不多说,先上效果图:
看完了效果,我们来分析一下如何实现,并且实现这个我们能学到什么。
如何实现:
仿照腾讯漫画的效果,实现toast从顶部出现,又从顶部消失,代码逻辑比较简单。只需要用到Android的补间动画。另外需要仿照原生的调用方法,一行代码实现调用,降低开发者学习成本。
学习目的:
主要是强调一个良好的封装和适配,在写这个控件的过程中充分理解Android补间动画,以及自定义View的使用。另外需要仿照原生的调用方法,一行代码实现调用。
代码结构:
代码非常简单,只需要两个类,一个ToastLayout继承自RelativeLayout,是一个自定义view,动画的逻辑可以写在里面。另外一个类则是ZToast,是暴露给开发者的类,里面有静态的方法将提供给开发者使用。另外还需要一个布局文件,自定义view将会使用到layout文件。代码以及讲解:
首先是布局文件,比较简单,不做详细说明:
<?xml version="1.0" encoding="utf-8"?>
ToastLayout:
package com.zhhr.custom;import android.content.Context;import android.util.AttributeSet;import android.view.LayoutInflater;import android.view.View;import android.view.animation.Animation;import android.view.animation.AnimationSet;import android.view.animation.TranslateAnimation;import android.widget.ImageView;import android.widget.RelativeLayout;import android.widget.TextView;import com.zhhr.R;/** * Created by 皓然 on 2017/8/3. */public class ToastLayout extends RelativeLayout { private static final int ANIMATION_TIME = 200; private TextView mContent; private View view; private boolean isShow; private RelativeLayout mWrapper; private ImageView mIcon; private int height; public boolean isShow() { return isShow; } public ToastLayout(Context context) { this(context, null); } public ToastLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ToastLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); view = LayoutInflater.from(getContext()).inflate(R.layout.layout_toast, null); addView(view); mContent = view.findViewById(R.id.tv_content); mWrapper = view.findViewById(R.id.rl_toast); mIcon = view.findViewById(R.id.iv_icon); height = 60; } public void setTextColor(int color){ mContent.setTextColor(color); } public void setBgColor(int color){ mWrapper.setBackgroundColor(color); } public void setIconVisible(boolean isShow){ if(isShow){ mIcon.setVisibility(View.VISIBLE); }else { mIcon.setVisibility(View.GONE); } } public void setIcon(int resId){ mIcon.setImageResource(resId); } public void setHeight(int height) { this.height = height; } public void setContent(String content){ if(mContent!=null){ mContent.setText(content); } } public void showToast(long time){ AnimationSet animationSet = new AnimationSet(true); TranslateAnimation trans1 = new TranslateAnimation(0, 0 ,-dip2px(getContext(),height) ,0); TranslateAnimation trans2 = new TranslateAnimation(0,0 , 0 , -dip2px(getContext(),height)); trans1.setDuration(ANIMATION_TIME); trans2.setStartOffset(ANIMATION_TIME+time); trans2.setDuration(ANIMATION_TIME); animationSet.addAnimation(trans1); animationSet.addAnimation(trans2); this.startAnimation(animationSet); animationSet.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { isShow = true; ToastLayout.this.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animation animation) { isShow = false; ToastLayout.this.setVisibility(View.GONE); } @Override public void onAnimationRepeat(Animation animation) { } }); } /** * 将dip或dp值转换为px值,保证尺寸大小不变 * @param context * @param dipValue * @return */ public static int dip2px(Context context, float dipValue) { float density = context.getResources().getDisplayMetrics().density; return (int) (dipValue * density + 0.5f); }}
主要在showToast方法里写了两个动画,这里用到了Android的补间动画,详细的说明可以参考博客Android开发——View动画、帧动画和属性动画详解,这里使用的是位移动画TranslationAnimation,分别设置了进入动画trans1和退出动画trans2,再通过动画的持续时间来控制toast的弹出和退出。
ZToast:
package com.zhhr.custom;import android.app.Activity;import android.view.ViewGroup;import android.widget.RelativeLayout;import com.zhhr.R;/** * Created by zhhr on 2018/3/27. */public class ZToast { private Activity mActivity; private RelativeLayout mToastLayout; private ToastLayout mToast; private ViewGroup mView; private String text; private long times; private static ZToast mToastInstance; /** * 固定参数 */ public static final long LENGTH_LONG = 3000; public static final long LENGTH_SHORT = 1000; /** * 静态可设置参数 */ public static int TextColor; public static int BgColor; public static boolean isShowIcon = true; public static int height = 60; public static int resId; /** * 设置小图标 * @param resId */ public static void setResId(int resId) { ZToast.resId = resId; } /** * 设置高度 * @param height */ public static void setHeight(int height) { ZToast.height = height; } /** * 图标是否显示 * @param isShowIcon */ public static void setIsShowIcon(boolean isShowIcon) { ZToast.isShowIcon = isShowIcon; } /** * 背景色 * @param bgColor */ public static void setBgColor(int bgColor) { BgColor = bgColor; } /** * 文字颜色 * @param textColor */ public static void setTextColor(int textColor) { TextColor = textColor; } /** * 初始化 * @param BgColor 背景颜色 * @param TextColor 文字颜色 * @param isIcon 图标是否显示 * @param resId 图标 * @param height 高度 */ public static void init(int BgColor, int TextColor, boolean isIcon,int resId,int height){ ZToast.BgColor = BgColor; ZToast.TextColor = TextColor; ZToast.isShowIcon = isIcon; ZToast.height = height; ZToast.resId = resId; } /** * 构造函数,上下文为activity * @param mActivity * @param text * @param times */ public ZToast(Activity mActivity, String text, long times){ this.mActivity = mActivity; this.text = text; this.times = times; } /** * 构造函数,上下文为View * @param mView * @param text * @param times */ public ZToast(ViewGroup mView, String text, long times){ this.mView = mView; this.text = text; this.times = times; } /** * 调用方法,上下文为activity * @param mActivity * @param text * @param times * @return */ public static ZToast makeText(Activity mActivity, String text, long times){ mToastInstance = new ZToast(mActivity,text,times); return mToastInstance; } /** * 调用方法,上下文为view * @param mView * @param text * @param times * @return */ public static ZToast makeText(ViewGroup mView, String text, long times){ mToastInstance = new ZToast(mView,text,times); return mToastInstance; } /** * 展示 */ public void show(){ if(mActivity!=null){ mToastLayout = (RelativeLayout) mActivity.findViewById(R.id.rl_toast); if(mToastLayout==null){//判断是否已经添加进母VIEW里,没有则添加进去 mToast = new ToastLayout(mActivity); initToast(mToast); mActivity.addContentView(mToast,new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ToastLayout.dip2px(mActivity,height))); }else{//如果有,则直接取出 mToast = (ToastLayout) mToastLayout.getParent(); } mToast.setContent(text); mToast.showToast(times); return; }else if(mView!=null){ mToastLayout = (RelativeLayout) mView.findViewById(R.id.rl_toast); if(mToastLayout==null){ mToast = new ToastLayout(mView.getContext()); initToast(mToast); mView.addView(mToast,new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ToastLayout.dip2px(mView.getContext(),height))); }else{ mToast = (ToastLayout) mToastLayout.getParent(); } mToast.setContent(text); mToast.showToast(times); } } /** * 设置各个参数 * @param mToast */ private void initToast(ToastLayout mToast) { if(TextColor!=0){ mToast.setTextColor(TextColor); } if(BgColor!=0){ mToast.setBgColor(BgColor); } if(resId!=0){ mToast.setIcon(resId); } mToast.setIconVisible(isShowIcon); mToast.setHeight(height); } private boolean isShowToast(){ if(mToast == null){ return false; } return mToast.isShow(); } /** * 是否在展示 * @return */ public static boolean isShow(){ if(mToastInstance == null){ return false; }else{ boolean isShow = mToastInstance.isShowToast(); mToastInstance = null; return isShow; } }}
这个类主要是做了一些基本的封装,我们要基本仿照原生toast的写法,降低开发者的学习成本,所以采用ZToast.makeText(MainActivity.this,"文字",ZToast.LENGTH_SHORT).show(); 这样的方法来进行实现。首先判断上下文是activity还是自定义view,这里提供了两套构造方法。如果是activity,则通过addContentView()的方法把我们刚刚写完的自定义view添加进activity里;如果是view,则通过addview()方法把自定义view添加进去,再调用时候自定义view的showToast()方法来进行动画的播放。
还可以通过init方法和各种set方法来手动设置自定义view的属性。
实现:
显示toast
在activity中使用
ZToast.makeText(MainActivity.this,"文字",ZToast.LENGTH_SHORT).show();
在fragment中使用
ZToast.makeText(getActivity(), "文字",1000).show();
在自定义View中使用
ZToast.makeText((ViewGroup) getParent(),"文字"",1000).show();
设置toast的各个参数
在调用makeText方法之前调用init方法来设置参数ZToast.init(Color.parseColor("#000000"),Color.parseColor("#ff00ff"),true,R.mipmap.item_pasue,90);//参数为 背景色 文字颜色 是否有图标 图标资源 高度//也可以单独设置参数,如高度ZToast.setHeight(200);//最后调用toast方法ZToast.makeText(MainActivity.this,"点击事件",ZToast.LENGTH_SHORT).show();
判断是否正在显示toast
ZToast.isShow()当isShow()为true时,则说明正在显示中,可以用来做双击退出
点击两下back按键退出可以这样写:
代码如下:public boolean onKeyDown(int keyCode, KeyEvent event) { if((keyCode == KeyEvent.KEYCODE_BACK)){ if(ZToast.isShow()){ return super.onKeyDown(keyCode, event); }else{ ZToast.makeText(MainActivity.this,"再按一次返回键退出",1000).show(); return false; } }else{ return super.onKeyDown(keyCode, event); }}
PS:推荐使用无actionbar的主题和设置statusbar颜色
强烈建议搭配无actionbar的主题来使用在style.xml中:最好再设置一下statusbar的颜色在activity中:Window window = getWindow(); window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(Color.TRANSPARENT);
总结:
自定义view的使用已经仿照原生控件的封装方式,极大的简化了我们代码,为项目中今后所有需要用到toast的地方都提供了完整的一套方案。掌握的并不是说代码的技术含量,而是一种封装的思路。
完整的DEMO代码已经提交到GitHub和Jcenter。GITHUB地址:ZToastDemo
安装方法:
compile 'com.zhhr:ztoast:1.0.0'
更多相关文章
- [Android]用架构师角度看插件化(2)-Replugin 唯一hook点
- android listview为什么会执行很多次,频繁调用getview
- Android绘图机制(一)——自定义View的基础属性和方法
- Android中使用SAX对XMl文件进行解析
- Android仿搜狗浏览器加载动画
- [置顶] Android中调用系统相机、系统相册来获取图片,并裁剪图片。
- Android优雅地处理按钮重复点击
- Android(安卓)ContentProvider数据共享全解析
- # Android的按键消息分发机制