美文网首页自定义控件AndroidAndroid
Android实现探照灯的功能

Android实现探照灯的功能

作者: 雯艺雪 | 来源:发表于2019-06-05 19:29 被阅读138次

    一、前言

    由于最近项目需要,急需开发一个探照灯的功能,限定三天内完成,在网上搜了一整天,尝试各种实现方法,其实无外乎使用BitmapShader和ShapeDrawable在画布上画出源图片,还有一些使用其他语言的方法,比如canavas的clip相关的方法,但是都不能满足我的项目需求。第一个方法虽然可以实现探照灯的效果,缺点是必须要有源图像,而我都项目要求是不管你在哪个界面,都要能够直接探照到背景(就相当于被探照部分变透明了,直接看到后面)。最后只能想办法自己实现了,于是第二天想出了一个可行的方案,如下:

    二、思路

    根据需求,总结如下:

    -探照灯功能
    -探照灯可移动
    -探照的部分透明
    -在其他应用界面仍然可用

    三、实现

    废话不多说,直接上代码:

    -1.实现Activity透明
    <style name="TranspantTheme">
            <item name="windowActionBar">false</item>
            <item name="windowNoTitle">true</item>
            <item name="android:windowBackground">@android:color/transparent</item>
            <item name="android:colorBackgroundCacheHint">@null</item>
            <item name="android:windowIsTranslucent">true</item>
            <item name="android:windowAnimationStyle">@android:style/Animation</item>
            <item name="android:windowNoTitle">true</item>
            <item name="android:windowContentOverlay">@null</item>
        </style>
    

    在配置文件中给Activity添加如上主题,即将

    android:theme="@style/theApp"
    

    改为:

    android:theme="@style/TranspantTheme"
    

    这样子启动起来的Activity就是个透明的。

    -2.退出应用仍然可用(在其他应用界面仍然可用)

    使用WindowManager:

     private WindowManager windowManager;
     private WindowManager.LayoutParams wmParams,wm;
     private int wmType;
     private void initView() {
          
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                wmType = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
            } else {
                wmType = WindowManager.LayoutParams.TYPE_PHONE;
            }
    
            windowManager = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
    
            wmParams = new WindowManager.LayoutParams();
            wmParams.type = wmType;
            wmParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
            wmParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            wmParams.gravity = Gravity.LEFT | Gravity.TOP;
            wmParams.x = 0;
            wmParams.y = 0;
            wmParams.width = WindowManager.LayoutParams.MATCH_PARENT;
            wmParams.height = WindowManager.LayoutParams.MATCH_PARENT;
            wmParams.format = PixelFormat.RGBA_8888;
    
            wm = new WindowManager.LayoutParams();
            wm.type = wmType;
            wm.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
            wm.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            wm.gravity = Gravity.RIGHT | Gravity.TOP;
            wm.x = 0;
            wm.y = 0;
            wm.width = 100;
            wm.height = 100;
            wm.format = PixelFormat.RGBA_8888;
        }
    

    先初始化好windowManager,别忘了在Activity配置文件中设置为横屏显示(受到windowManager影响)

    -3.定义实现探照灯功能的View

    直接上代码:

    public class MyImageView extends android.support.v7.widget.AppCompatImageView{
        private Paint paint1,paint2;//两只笔
        private float x=200,y=200;
        private int screenWidth,screenHeight;
    
        public MyImageView(Context context) {
            super(context);
            initData();
        }
    
        private synchronized void initData() {
            //获取屏幕宽高
            DisplayMetrics metrics = getResources().getDisplayMetrics();
            screenWidth = metrics.widthPixels;
            screenHeight = metrics.heightPixels;
            setMyEraseSize(40);
            setColor(Color.GRAY);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            drawBackground(canvas);
            drawMyCircle(canvas);
        }
    
        //画圆
        private void drawMyCircle(Canvas canvas) {
            canvas.drawCircle(x,y,100,paint1);
        }
    
        //画背景颜色
        private void drawBackground(Canvas canvas) {
            Rect rect=new Rect(0,0,screenWidth,screenHeight);
            canvas.drawRect(rect,paint2);
        }
    
        public boolean onTouchEvent(MotionEvent event){
            final int x = (int) event.getX();// 获取当前触摸点的X轴坐标
            final int y = (int) event.getY(); // 获取当前触摸点的Y轴坐标
            this.x=x;this.y=y;
            invalidate(); // 重绘画布
            return true;
        }
    
        //设置橡皮擦大小
        public void setMyEraseSize(int size){
            paint1= new Paint();
            paint1.reset();
            paint1.setAntiAlias(true);//抗锯齿
            paint1.setDither(true);//防抖动
            paint1.setColor(Color.WHITE);
            paint1.setStrokeWidth(size);
            paint1.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
            BlurMaskFilter bmf=new BlurMaskFilter((0.5f), BlurMaskFilter.Blur.SOLID);
            paint1.setMaskFilter(bmf);
            paint1.setStyle(Paint.Style.FILL);
            paint1.setStrokeJoin(Paint.Join.ROUND);
            paint1.setStrokeCap(Paint.Cap.ROUND);
            paint1.setSubpixelText(true);
            paint1.setTextSize(50);
        }
    
        //设置比颜色
        public void setColor(int color){
            paint2=new Paint();
            paint2.reset();
            paint2.setAntiAlias(true);//抗锯齿
            paint2.setDither(true);//防抖动
            paint2.setStrokeWidth(5);
            paint2.setTextSize(50);
            paint2.setSubpixelText(true);
            BlurMaskFilter bmf=new BlurMaskFilter((0.5f), BlurMaskFilter.Blur.SOLID);
            paint2.setMaskFilter(bmf);
            paint2.setPathEffect(new CornerPathEffect(20));
            paint2.setStyle(Paint.Style.FILL);
            paint2.setStrokeJoin(Paint.Join.ROUND);
            paint2.setStrokeCap(Paint.Cap.ROUND);
            paint2.setColor(color);
        }
    }
    

    解析:
    简单来说,其实就是采用思路:
    用两只笔,一只负责画背景(黑布),一只负责橡皮擦的功能(画圆),这时由于Activity是透明的,所以就可以直接看到下一层内容,然后在手指移动时更新橡皮擦(圆形)的位置,重绘就可以了。代码很简单,相信你能看懂!!
    怎么样?是不是很简单?
    为了能让大家看到效果,这里接着加上这个demo的其他方法:

    public class MainActivity extends Activity {
        private WindowManager windowManager;
        private WindowManager.LayoutParams wmParams,wm;
        private int wmType;
        private MyImageView myImageView;
        private ImageView view;
        private final int OVERLAY_PERMISSION_REQ_CODE = 1234;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            if (checkBallPermission()) startProgram();
        }
    
       //检查是否有悬浮窗权限
        private boolean checkBallPermission()
        {
            if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
            {
                if(!Settings.canDrawOverlays(this)) {
                    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
                    startActivityForResult(intent,OVERLAY_PERMISSION_REQ_CODE);
                    return false;
                }
            }
            return true;
        }
    
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            Log.e("SearchPrint","onActivityResult");
            if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
                if(android.os.Build.VERSION.SDK_INT>=Build.VERSION_CODES.M) {
                    if (!Settings.canDrawOverlays(this))
                    {
                        Toast.makeText(MainActivity.this, this.getResources().getString(R.string.ballfail), Toast.LENGTH_SHORT).show();
                        this.finish();
                    }
                    else {
                        startProgram();
                    }
                }
            }
        }
    
        private void startProgram() {
            initView();
            show();
            addListener();
        }
    
        private void addListener() {
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    hide();
                    finish();
                }
            });
        }
    
        private void hide(){
            try{
                windowManager.removeView(myImageView);
                windowManager.removeView(view);
            }catch (Exception e){
                e.printStackTrace();
                Log.e("SearchPrint","removeView:"+e.getMessage());
            }
        }
    
        private void show() {
            try{
                windowManager.addView(myImageView,wmParams);
                windowManager.addView(view,wm);
            }catch (Exception e){
                e.printStackTrace();
                Log.e("SearchPrint","addView:"+e.getMessage());
            }
        }
    
        private void initView() {
            myImageView=new MyImageView(this);
            view=new ImageView(this);
            view.setBackgroundResource(R.drawable.exit);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                wmType = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
            } else {
                wmType = WindowManager.LayoutParams.TYPE_PHONE;
            }
    
            windowManager = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
    
            wmParams = new WindowManager.LayoutParams();
            wmParams.type = wmType;
            wmParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
            wmParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            wmParams.gravity = Gravity.LEFT | Gravity.TOP;
            wmParams.x = 0;
            wmParams.y = 0;
            wmParams.width = WindowManager.LayoutParams.MATCH_PARENT;
            wmParams.height = WindowManager.LayoutParams.MATCH_PARENT;
            wmParams.format = PixelFormat.RGBA_8888;
    
            wm = new WindowManager.LayoutParams();
            wm.type = wmType;
            wm.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
            wm.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            wm.gravity = Gravity.RIGHT | Gravity.TOP;
            wm.x = 0;
            wm.y = 0;
            wm.width = 100;
            wm.height = 100;
            wm.format = PixelFormat.RGBA_8888;
        }
    }
    
    

    四、其他

    如果需要实现探照出来的是一张自定义的图片,那更简单,直接结合使用BitmapShader和ShapeDrawable,网上有不少方法,你还可以添加个从图库里面选择图片的方法,都挺简单的,由于篇幅限制,这里就不再叙述了。

    原创文章,转载请加上链接https://www.jianshu.com/p/438e5c7f3ba7

    相关文章

      网友评论

        本文标题:Android实现探照灯的功能

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