【Android自定义View】 仿照腾讯漫画自定义Toast的实现

效果展示:

话不多说,先上效果图:



 看完了效果,我们来分析一下如何实现,并且实现这个我们能学到什么。

如何实现:

   仿照腾讯漫画的效果,实现toast从顶部出现,又从顶部消失,代码逻辑比较简单。只需要用到Android的补间动画。另外需要仿照原生的调用方法,一行代码实现调用,降低开发者学习成本。

学习目的:

  主要是强调一个良好的封装和适配,在写这个控件的过程中充分理解Android补间动画,以及自定义View的使用。另外需要仿照原生的调用方法,一行代码实现调用。

代码结构:

【Android自定义View】 仿照腾讯漫画自定义Toast的实现_第1张图片
     代码非常简单,只需要两个类,一个ToastLayout继承自RelativeLayout,是一个自定义view,动画的逻辑可以写在里面。另外一个类则是ZToast,是暴露给开发者的类,里面有静态的方法将提供给开发者使用。另外还需要一个布局文件,自定义view将会使用到layout文件。


代码以及讲解:

首先是布局文件,比较简单,不做详细说明:



    
        
        

    

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,学习)