zoukankan      html  css  js  c++  java
  • <Android 应用 之路> 简易手电筒

    前言

    快一个月没有写自己的博客了,由于最近换了工作,换了居住地,所以有一些杂事需要处理,从今天开始恢复正常,不赘述了。进入今天的主题 —– 简易的手电筒。

    这个Demo中使用的是比较新的API,M版本之后添加的针对于手电筒的接口。这里使用的是Camera的API2接口,主要使用CameraManager中针对于闪光灯的一些方法,对于Camera API2的接口,后面在涉及相机应用的时候,API1和API2应该都会梳理一下,到时候再仔细的研究一下。


    思路

    实现一个简单的手电筒,考虑到M版本上新增的接口,可以直接通过setTorchMode来改变闪光灯的状态,实现开关,然后根据当前的闪光灯状态,有回调函数,若其他的应用打开了闪光灯或者是关闭了闪光灯,该应用要作出对应的调整,同时,开启和关闭的过程,需要有明显的用户感知和提示,这就要结合NotificationManager和CameraManager的接口一起实现了。


    接口介绍

    CameraManager.java(frameworks/base/core/java/android/hardware/camera2)
    这里写图片描述

    方法 含义
    TorchCallback 针对闪光灯的回调
    AvailabilityCallback 针对相机是否可用的回调
    CameraManager() 构造函数
    getCameraIdList() 获取相机的Id
    registerAvailabilityCallback() 注册相机是否可用的回调
    unregisterAvailabilityCallback() 解除注册
    registerTorchCallback() 注册针对闪光灯状态的回调
    unregisterTorchCallback() 解除注册
    getCameraCharacteristics() 传入参数为相机的id,获取相机的一些参数信息,如支持的预览大小,支持的滤镜等等
    openCamera() 传入的参数为相机的id和状态的回调StateCallback,这个是在CameraDevice中定义的,打开相机操作
    setTorchMode() 设置闪光灯的状态

    实战代码

    1.布局文件

    由于是手电筒,布局文件很简单,主布局中只有一个button
    activity_custom_button.xml

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/flash_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/black"
        tools:context=".FlashActivity">
    
        <Button
            android:id="@+id/bt_flash"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_gravity="center"
            android:background="@drawable/flash_open" />
    
    </FrameLayout>

    显示当前闪光灯被占用的自定义Toast布局
    busy_toast.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="match_parent"
        android:orientation="vertical">
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/busy_snackbar_bg"
            android:gravity="center"
            android:padding="@dimen/activity_horizontal_margin"
            android:text="FlashLight is Busy , Sorry!"
            android:textStyle="bold" />
    
    </LinearLayout>

    2.代码文件

    主要就是两个类,一个是主Activity,一个就是用来执行notification的pendingintent的广播接收器
    FlashActivity.java

    package mraz.com.custombutton;
    
    import android.annotation.TargetApi;
    import android.app.Notification;
    import android.app.NotificationManager;
    import android.app.PendingIntent;
    import android.content.Context;
    import android.content.Intent;
    import android.content.res.Resources;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.hardware.camera2.CameraAccessException;
    import android.hardware.camera2.CameraManager;
    import android.os.Build;
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.support.v7.app.NotificationCompat;
    import android.view.Gravity;
    import android.view.View;
    import android.widget.Button;
    import android.widget.FrameLayout;
    import android.widget.Toast;
    
    @TargetApi(Build.VERSION_CODES.M)
    public class FlashActivity extends AppCompatActivity {
    
        public static final String CLOSE_FLASH_ACTION = "android.intent.action.close_flash";
        private static final int NOTIFICATIONID = 0;
        private Button btFlash;
        private boolean mIsFlashOn = false;
        private CameraManager cameraManager = null;
        private String[] mCameraIds;
        private Notification mFlashOnNotification = null;
        private NotificationManager notificationManager = null;
        private boolean isFlashAvailbale = true;
        private FrameLayout mContentPanel = null;
    
    
        //闪光灯状态变化的回调
        private CameraManager.TorchCallback torchCallback = new CameraManager.TorchCallback() {
            @Override
            public void onTorchModeUnavailable(String cameraId) {
                super.onTorchModeUnavailable(cameraId);
                //onTorchModeUnavailable 当前闪光灯不可用,如果当前闪光处于打开状态,则关闭它,并且对应的标志位
                if (cameraId.equals(mCameraIds[0]) && mIsFlashOn) {
                    reverseFlashState();
                }
                isFlashAvailbale = false;
                System.out.println("cameraId = " + cameraId + " onTorchModeUnavailable");
            }
    
            @Override
            public void onTorchModeChanged(String cameraId, boolean enabled) {
                super.onTorchModeChanged(cameraId, enabled);
                //onTorchModeChanged 闪光灯状态变化回调 enabled=false 闪光灯关闭
                //enabled=true 闪光灯已经开启
                //通过这个回调设置标志位,如果当前闪光灯开着但是收到了闪光灯已经被关闭的回调,则改变对应的状态
                isFlashAvailbale = true;
                System.out.println("cameraid = " + cameraId + " enabled = " + enabled + " misFlashOn = " + mIsFlashOn);
                if (cameraId.equals(mCameraIds[0]) && enabled == false && mIsFlashOn) {
                    reverseFlashState();
                }
                System.out.println("cameraId = " + cameraId + " onTorchModeChanged enabled = " + enabled);
            }
        };
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_custom_button);
    
            //闪光灯开关按钮
            btFlash = (Button) findViewById(R.id.bt_flash);
            //整个布局
            mContentPanel = (FrameLayout) findViewById(R.id.flash_content);
    
            btFlash.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    reverseFlashState();
                }
            });
            //根据当前闪光灯装填设置一下UI界面的颜色
            changeFlashUi(mIsFlashOn);
        }
    
        //flash状态翻转
        private void reverseFlashState() {
            //如果当前Flash 处于unavailable状态,说明当前闪光灯被占用,无法使用
            if (!isFlashAvailbale) {
                //显示当前闪光灯被占用的提示
                showFlashBusy();
                return;
            }
            changeFlashState(mIsFlashOn);//开->关  关->开
            mIsFlashOn = !mIsFlashOn;//标志位装换
            changeFlashUi(mIsFlashOn);//界面UI切换,这里主要就是为了突出闪光灯开关的状态不同
            applyNotification(mIsFlashOn);//闪光灯开启的提示显示和消除
        }
    
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
        private void applyNotification(boolean isFlashOn) {
            if (!isFlashOn) {
                dismissNotification();
                return;
            }
            if (mFlashOnNotification != null && notificationManager != null) {
                notificationManager.notify(NOTIFICATIONID, mFlashOnNotification);
            }
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
            notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            cameraManager.registerTorchCallback(torchCallback, null);//注册回调
            getCameraList();//获取当前手机的摄像头个数
            generateNotify();//生成需要显示的提示,方便后面显示
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            cameraManager.unregisterTorchCallback(torchCallback);//ondestory的时候解除回调
        }
    
        @TargetApi(Build.VERSION_CODES.M) //只有M版本的手机可以使用这个方法
        private void changeFlashState(boolean isFlashOn) {
            if (cameraManager != null && mCameraIds != null) {
                try {
                    cameraManager.setTorchMode(mCameraIds[0], !isFlashOn);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)  //只有L版本的收集可以使用这个方法
        private void getCameraList() {
            if (cameraManager != null) {
                try {
                    mCameraIds = cameraManager.getCameraIdList();
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    
        //按钮的背景图切换
        private void changeFlashUi(boolean isFlashOn) {
            if (isFlashOn) {
                btFlash.setBackgroundResource(R.drawable.flash_open);
            } else {
                btFlash.setBackgroundResource(R.drawable.flash_close);
            }
        }
    
        //生成notification的大图标
        private Bitmap createNotificationLargeIcon(Context c) {
            Resources res = c.getResources();
            int width = (int) res.getDimension(android.R.dimen.notification_large_icon_width);
            int height = (int) res.getDimension(android.R.dimen.notification_large_icon_height);
            Bitmap result = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(res, R.mipmap.ic_flash_on_normal), width, height, false);
            return result;
        }
    
        //生成notification
        private void generateNotify() {
            if (mFlashOnNotification != null) return;
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
            builder.setLargeIcon(createNotificationLargeIcon(this))
                    .setContentTitle("手电筒已开启")
                    .setContentText("点击可关闭手电筒")
                    .setSmallIcon(R.mipmap.ic_flash_off_normal, 3)
                    .setContentIntent(createCloseFlashPendingIntent());
            mFlashOnNotification = builder.build();
        }
    
        //消除notification
        private void dismissNotification() {
            if (notificationManager != null && mFlashOnNotification != null) {
                notificationManager.cancel(NOTIFICATIONID);
            }
        }
    
        //创建点击notification对应的PendingIntent
        private PendingIntent createCloseFlashPendingIntent() {
            Intent intent = new Intent();
            intent.setClass(this, FlashCloseReceiver.class);
            intent.setAction(CLOSE_FLASH_ACTION);
    
            return PendingIntent.getBroadcast(this, 0, intent, 0);
        }
    
        //显示一个手电筒忙碌的提示
        private void showFlashBusy() {
            View toastContent = getLayoutInflater().inflate(R.layout.busy_toast, null, false);
            Toast toast = new Toast(this);
            toast.setView(toastContent);
            toast.setGravity(Gravity.CENTER, 0, 0);
            toast.setDuration(Toast.LENGTH_LONG);
            toast.show();
        }
    }

    FlashCloseReceiver.java

    package mraz.com.custombutton;
    
    import android.annotation.TargetApi;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.hardware.camera2.CameraAccessException;
    import android.hardware.camera2.CameraManager;
    import android.os.Build;
    
    public class FlashCloseReceiver extends BroadcastReceiver {
        CameraManager mCameraManager = null;
        String[] mCameraIds = null;
    
        public FlashCloseReceiver() {
        }
    
        @TargetApi(Build.VERSION_CODES.M)
        @Override
        public void onReceive(Context context, Intent intent) {
            System.out.println("onReceiver");
            mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
            if (mCameraManager != null) {
                try {
                    mCameraIds = mCameraManager.getCameraIdList();
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            }
            String action = intent.getAction();
            if (action.equals(FlashActivity.CLOSE_FLASH_ACTION)) {
                if (mCameraManager != null && mCameraIds != null && mCameraIds.length != 0) {
                    try {
                        System.out.println("setTorchMode");
                        mCameraManager.setTorchMode(mCameraIds[0], false);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    实际效果图

    手电筒关闭状态

    这里写图片描述

    手电筒开启状态

    这里写图片描述

    手电筒开启状态提示信息

    这里写图片描述


    备注

    由于开发时间比较短,测试可能不充分,有问题欢迎留言讨论~

  • 相关阅读:
    VS开发ExtJS
    ASP.NET初识9
    程序窗体和对话框
    浏览器的使用
    ASP.NET初识6
    310号的收获 将会决定 业余时间的方向。now foucs
    无代码 无说服力 4种 cast
    android Makefile 的流程—how android makefile workmakefile progress in android
    【资料】父子进程可以共享变量吗? 使用pipeline
    Sysinternals 手册阅读 about vmmap目标运行中优化内存
  • 原文地址:https://www.cnblogs.com/lanzhi/p/6467185.html
Copyright © 2011-2022 走看看