美文网首页Android开发Android开发经验谈Android技术知识
Android自定义view之实现带checkbox的Snack

Android自定义view之实现带checkbox的Snack

作者: Android高级开发 | 来源:发表于2019-01-15 16:46 被阅读32次

    前言

    最近项目要求实现一个类似于snackbar功能,但是又不完全是snackbar的插件,本来想在Google提供的snackbar里面进行更改,但是这样太麻烦了,于是自己动手实现了一个snackbar。先看下效果图:


    image.png

    1.要解决的问题

    1.弹框里面除了文字提示之外还有一个按钮,这个按钮是一个checkbox,分为选择状态和非选择状态。 2.弹框底部还有一个布局,弹框要在底部布局之上。 3.弹框消失的时间。 4.当有虚拟键的时候,弹框应该在虚拟键之上。

    2、分析与实现

    当在任何一个activity中时,自定义的Snackbar应该在activity 布局的上面显示,如下图所示:


    image.png

    绿色部分是自定义的snackbar的布局,红色部分是activity布局,部分已经被snackbar遮挡。要实现上面的效果,就要获取到当前activity的布局,流程很简单:


    image.png

    1、获取activity的根布局

    Window window=(Activity) context).getWindow();//获取当前activity的window
    ViewGroup decorView= (ViewGroup) window.getDecorView();//获取activity的跟布局
    

    activity的view的原理可以看下面的图:

    image.png

    activity的顶级布局是DecorView,其中DecorView类由PhoneWindow所持有,Phonewindow由Activity持有,所以上面获取DecorView时要通过Activity获取window,再由window获取得到。其中DecorView中包括一个TitleVIew和ContentView,ContenView中的布局是通过在Activity中的setContentView()来设置的。 因为自定义的snackbar要放在整个activity的根布局之上,因此就是将snackbar的view加到DecorView上,而不是ContentView上面。
    2、获取snackbar的view

            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            //parent是上面的获取的activity的跟布局
            View snackbarView= inflater.inflate(R.layout.layout_mobile_notify_snackbar, parent, false);
            //R.layout.layout_mobile_notify_snackbar snackbar的布局
    

    layout_mobile_notify_snackbar.xml布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="78dp"
        android:layout_gravity="bottom"
        android:gravity="center_vertical"
        android:orientation="vertical">
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="30dp"
            android:background="@drawable/diffussion_background"
            />
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:layout_gravity="bottom"
            android:background="@color/white"
            android:gravity="center_vertical"
            android:orientation="horizontal"
            >
    
        <TextView
            android:layout_weight="1"
            android:id="@+id/notify_text"
            android:layout_width="165dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:text="移动网络下视频自动播放"
            android:textSize="15dp" />
    
        <CheckBox
            android:checked="true"
            android:id="@+id/notify_checkbox"
            style="@style/SettingsMobileNotifyCheckBoxStyle"
            android:layout_width="36dp"
            android:layout_height="36dp"
            android:layout_marginRight="15dp" />
        </LinearLayout>
    </LinearLayout>
    
    3、将snackbar的布局加到activity的根布局

    获取到了snackbar的布局后,要在snackbar显示的时候将snackbar的布局显示在activity的DecorView之上,snackbar消失的时候从activity的根布局上移除。因此可以snackbar的show方法如下:

            public void show()
            {
                 ViewGroup.LayoutParams layoutParams = snackbarView.getLayoutParams();
                 if (layoutParams instanceof FrameLayout.LayoutParams) {
                    FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) layoutParams;
                    lp.gravity = Gravity.BOTTOM;
                    snackbarView.setLayoutParams(lp);
                }
                //设置snackbar的布局参数为从decorView的底部弹出
                decorView.addView(snackbarView);
                //将snackbarView加到Activity的根布局上 
                decorView.requestLayout();    
    }
              
    

    snackbar一般是显示一段时间后自动消失的,因此还要定时从decorView上移除。消失方法dismiss如下:

        public void dismiss() {  
            decorView.removeView(snackbarView);
        }
    

    有了显示和消失的方法后,就要考虑怎样实现snackbar显示后如何定时的自动消失,完善后的show方法如下:

        public void show(Activity activity) {
            ViewGroup.LayoutParams layoutParams = snackbarView.getLayoutParams();
            if (layoutParams instanceof FrameLayout.LayoutParams) {
                FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) layoutParams;
                lp.gravity = Gravity.BOTTOM;
                snackbarView.setLayoutParams(lp);
            }
           //设置snackbar的布局参数为从decorView的底部弹出
            decorView.addView(snackbarView);
            //将snackbarView加到Activity的根布局上 
            snackbarView.bringToFront();//将snackbar的view移动到最上面,以免被其他的view遮挡
            snackbarView.requestLayout();
            dercorView.invalidate();
            //设置定时消失
            dercorView.postDelayed(mDelayedDismissAction, 5000);//snackbar消失的时间
        }
        //延时消失  
        private Runnable mDelayedDismissAction = new Runnable() {
            @Override
            public void run() {
                dismiss();//消失
            }
        };
    

    在show代码中通过dercorView.postDelayed(mDelayedDismissAction, 5000);抛出一个定时消失的Runnable,这样就可以实现snackbar显示一定时间后自动消失。
    注意:上面代码中的snackbarView.bringToFront();的作用是将snackbar的view放到最上面,防止被其他的view遮挡。

    4、将snackbar的布局设置在activity的某个位置上面

    上面实现了snackbar的显示和消失都是在activity的底部弹出,位置固定,既然是自己实现的snackbar当然可以将它设置显示在任何位置,下面的方法可以将snackbar显示在activity布局中的view的上方。

        private void above(View snackbarView, View targetView, int contentViewTop, Activity activity) {
            if (snackbarView!= null) {
                int[] locations = new int[2];
                targetView.getLocationOnScreen(locations);
                int snackbarHeight = 52;
                //必须保证指定View的顶部可见 且 单行Snackbar可以完整的展示
                if (locations[1] >= contentViewTop + snackbarHeight) {
                    ViewGroup.LayoutParams params = mContentView.getLayoutParams();
                    ((ViewGroup.MarginLayoutParams) params).setMargins(0, 0, 0, snackbarView.getResources().getDisplayMetrics().heightPixels - locations[1]);
                    mContentView.setLayoutParams(params);
                }
            }
        }
    

    上面通过获取制定view的位置,然后设置snackbarView的LayoutParams 参数,从而实现snackbarView展示在指定view的上方。某些手机上有虚拟键,这个时候可以通过获取虚拟键的高度,再设置上面的参数值就可以了,如下:

        private void above(View mContentView, View targetView, int contentViewTop, Activity activity) {
            if (mContentView != null) {
                int[] locations = new int[2];
                targetView.getLocationOnScreen(locations);
                int snackbarHeight = 52;
                //必须保证指定View的顶部可见 且 单行Snackbar可以完整的展示
                if (locations[1] >= contentViewTop + snackbarHeight) {
                    ViewGroup.LayoutParams params = mContentView.getLayoutParams();
                    ((ViewGroup.MarginLayoutParams) params).setMargins(0, 0, 0, mContentView.getResources().getDisplayMetrics().heightPixels - locations[1] + getNavigationBarHeight(activity));
                    mContentView.setLayoutParams(params);
                }
            }
        }
    

    上面的getNavigationBarHeight(activity)获取虚拟键的高度代码:

        private int getNavigationBarHeight(Activity activity) {
            WindowManager windowManager = activity.getWindowManager();
            Display d = windowManager.getDefaultDisplay();
            DisplayMetrics realDisplayMetrics = new DisplayMetrics();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                d.getRealMetrics(realDisplayMetrics);
            }
            int realHeight = realDisplayMetrics.heightPixels;
            DisplayMetrics displayMetrics = new DisplayMetrics();
            d.getMetrics(displayMetrics);
            int displayHeight = displayMetrics.heightPixels;
            return realHeight - displayHeight > 0 ? (realHeight - displayHeight) : 0;
        }
    
    5、给snackbar中的按钮设置点击监听事件

    为了控制snackbar中的checkbox给snackbar中的checkbox设置点击监听

    private void addListenerOnCheckBox(Listener listener) {
            checkBox.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    listener.onCheck(((CheckBox) view).isChecked());//listener中监听checkbox的按钮是否点击
                }
            });
        }
    interface Listener{
        void oncheck(boolean isChecked);
    }
    

    在addListenerOnCheckBox中加了监听Listener ,用户可以监听到checkbox的点击事件,调用的时候只用传入listener就可以了。

    完整代码

    1.snackbar.java文件
    
    
    import android.app.Activity;
    import android.content.Context;
    import android.content.res.Resources;
    import android.os.Build;
    import android.support.v7.widget.RecyclerView;
    import android.util.DisplayMetrics;
    import android.view.Display;
    import android.view.Gravity;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.view.Window;
    import android.view.WindowManager;
    import android.widget.CheckBox;
    import android.widget.FrameLayout;
    import android.widget.TextView;
    
    
    /**
     * Created by hanking on 2018/8/29.
     * @author hanking
     */
    public class MobileNotifySnackbar {
        private View mContentView;
        private ViewGroup mParentView;
        private static RecyclerView mRcyclerView;
        private static CheckBox checkBox;
        private Runnable mDelayedDismissAction = new Runnable() {
            @Override
            public void run() {
                dismiss();
            }
        };
        private View.OnClickListener mListener;
    
        public MobileNotifySnackbar(ViewGroup parent, View content) {
            mParentView = parent;
            mContentView = content;
            mContentView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mListener != null) {
                        mListener.onClick(mContentView);
                    }
                }
            });
        }
    
        /**
         * @throws Resources.NotFoundException 如果停留在插件的Activity, getCurrentTopActivity返回的是插件中的Activity, inflater#inflate方法也就找不到布局资源,会抛出Resources.NotFoundException
         */
        public static MobileNotifySnackbar make(Window window, String text, Context context) {
            ViewGroup parent = (ViewGroup) window.getDecorView();
            final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            final View content = inflater.inflate(R.layout.layout_mobile_notify_snackbar, parent, false);
            checkBox = content.findViewById(R.id.notify_checkbox);
            MobileNotifySnackbar snackbar = new MobileNotifySnackbar(parent, content);
            snackbar.setText(text);
            snackbar.addListenerOnCheckBox(checkBox, context);
            return snackbar;
        }
    
        public static MobileNotifySnackbar make(Window window, String text) throws Resources.NotFoundException {
            ViewGroup parent = (ViewGroup) window.getDecorView();
            final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            final View content = inflater.inflate(R.layout.layout_mobile_notify_snackbar, parent, false);
            checkBox = content.findViewById(R.id.notify_checkbox);
            MobileNotifySnackbar snackbar = new MobileNotifySnackbar(parent, content);
            snackbar.setText(text);
            mRcyclerView = recyclerView;
            snackbar.addListenerOnCheckBox(checkBox);
            return snackbar;
        }
    
    
    
        public MobileNotifySnackbar addListenerOnCheckBox(CheckBox checkBox, Context context) {
            checkBox.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (!((CheckBox) view).isChecked()) {
    
                    } else {
                    }
                }
            });
            return this;
        }
    
        public void show(Activity activity) {
            mParentView.addView(mContentView);
            ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
            if (layoutParams instanceof FrameLayout.LayoutParams) {
                FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) layoutParams;
                lp.gravity = Gravity.BOTTOM;
                mContentView.setLayoutParams(lp);
            }
            mMobileNotifyHelper.notifyLoginObservers(true);
            mMobileNotifyHelper.isShow = true;
            //R.id.layout_tab是在snackbar下面的布局
            above(mContentView, mParentView.findViewById(R.id.layout_tab), 0, activity);
           //解决snackbar被其他布局遮挡
            mContentView.bringToFront();
            mContentView.requestLayout();
            mParentView.invalidate();
            mParentView.removeCallbacks(mDelayedDismissAction);
            mParentView.postDelayed(mDelayedDismissAction, 4000);
        }
    
        public void dismiss() {
            mParentView.removeCallbacks(mDelayedDismissAction);
            mParentView.removeView(mContentView);
    
        }
    
        public MobileNotifySnackbar setText(String text) {
            TextView textView = mContentView.findViewById(R.id.notify_text);
            textView.setText(text);
            return this;
        }
    
        public MobileNotifySnackbar setAction(View.OnClickListener listener) {
            mListener = listener;
            return this;
        }
    
        public void above(View mContentView, View targetView, int contentViewTop, Activity activity) {
            if (mContentView != null) {
                int[] locations = new int[2];
                targetView.getLocationOnScreen(locations);
                int snackbarHeight = 52;
                //必须保证指定View的顶部可见 且 单行Snackbar可以完整的展示
                if (locations[1] >= contentViewTop + snackbarHeight) {
                    ViewGroup.LayoutParams params = mContentView.getLayoutParams();
                    ((ViewGroup.MarginLayoutParams) params).setMargins(0, 0, 0, mContentView.getResources().getDisplayMetrics().heightPixels - locations[1] + getNavigationBarHeight(activity));
                    mContentView.setLayoutParams(params);
                }
            }
        }
    //获取底部虚拟键的高度,如果没有就返回0
        public int getNavigationBarHeight(Activity activity) {
            WindowManager windowManager = activity.getWindowManager();
            Display d = windowManager.getDefaultDisplay();
            DisplayMetrics realDisplayMetrics = new DisplayMetrics();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                d.getRealMetrics(realDisplayMetrics);
            }
            int realHeight = realDisplayMetrics.heightPixels;
            DisplayMetrics displayMetrics = new DisplayMetrics();
            d.getMetrics(displayMetrics);
            int displayHeight = displayMetrics.heightPixels;
            return realHeight - displayHeight > 0 ? (realHeight - displayHeight) : 0;
        }
    }
    
    2.使用方法
    MobileNotifySnackbar snackbar = MobileNotifySnackbar.make(getActivity().getWindow(), "移动网络下视频自动播放");
    snackbar.Show(getActivity());
                     
    
    QQ图片20190110202918.jpg
    QQ图片20190110202923.png

    +qq群:853967238。获取以上高清技术思维图,以及相关技术的免费视频学习资料。

    相关文章

      网友评论

        本文标题:Android自定义view之实现带checkbox的Snack

        本文链接:https://www.haomeiwen.com/subject/kbmydqtx.html