show code block

2018年4月23日 星期一

Anadroid元件(Camera) — 客製化相機、使用相機的正確姿勢(二)

前言:

證件照相機



有一種相機,叫做證件相機!

這邊提供客製化相機的做法。

在網路上找資料很多,但一直沒有確切可以用的code。

不是不完整,不然就是blog寫的當下android版本太低已不適用現今環境。


發現網路還是有一篇能用的。

 https://blog.csdn.net/LHBTM/article/details/55505668

這篇不完整,但都把釣竿給你了。

如果你整篇複製貼上,你會發現拍完照後,並不會返回MainActivity,就卡在拍照頁。

仔細看一下Code,其實說明都很完整!

合併一下官方文擋獲取儲存File位置的方式,來修改這篇。


先佈局好畫面:
acitvity_main.xml

activity_main.xml
 
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.hello.kaiser.customcamera.MainActivity">

    <ImageView
        android:id="@+id/show_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:src="@drawable/ic_launcher_background"
        android:text="Hello World!" />

    <Button
        android:id="@+id/btn_take_pic"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/show_image"
        android:layout_centerHorizontal="true"
        android:text="拍照" />

</RelativeLayout> 

然後MainActivity.java
 
public class MainActivity extends AppCompatActivity {

    private Button mBtnPic;
    private ImageView mShowImage;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();


    }

    private void initView() {
        mBtnPic = (Button) findViewById(R.id.btn_take_pic);
        mShowImage = (ImageView) findViewById(R.id.show_image);
    }
}


沒啥功能。
接下來開始大家都很會的crtl+c 和 crtl+v吧
https://blog.csdn.net/LHBTM/article/details/55505668
把上面這篇全部搬到你個人的專案內。

會動到的地方

會動到的地方,大概會長這樣

比較需要注意的,就是xml的View

記得換成自己包名的位置


記得更換View的名稱




記得在MainActivity.java製作畫面轉移事件
 mBtnPic.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this, TakePicActivity.class));
            }
        }); 


此時,大致上完成了
開啟後你會發現,事情沒你想得順利XD

至少畫面出來了,是吧!




這像我上一篇講得不太一樣,上一篇甚至連權限都不用。

這邊因為是客製化,就必須加權限進來。


此時要加入三個權限:
寫入相機權限、寫入SDCard權限、和相機權限
 
    <!--寫入手機權限-->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!--相機權限-->
    <uses-permission android:name="android.permission.CAMERA" />

權限的部分就請參考:
http://nikeru8.blogspot.tw/2017/04/android-permission.html


權限加入 MainActivity.java
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private Activity activity;
    public static final int PermissionCode = 1000;
    public static final int GetPhotoCode = 1001;

    private Button mBtnPic;
    private ImageView mShowImage;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        activity = this;
        initView();
        initListener();

    }

    private void initView() {
        mBtnPic = (Button) findViewById(R.id.btn_take_pic);
        mShowImage = (ImageView) findViewById(R.id.show_image);
    }

    private void initListener() {
        mBtnPic.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                //檢查是否取得權限
                final int permissionCheck = ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA);

                //沒有權限時
                if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(MainActivity.this,
                            new String[]{Manifest.permission.CAMERA,
                                    Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS,
                                    Manifest.permission.WRITE_EXTERNAL_STORAGE},
                            PermissionCode);
                } else { //已獲得權限
                    Toast.makeText(activity, "已經拿到權限囉!", Toast.LENGTH_SHORT).show();
                    startActivityForResult(new Intent(MainActivity.this, TakePicActivity.class), GetPhotoCode);
                }

            }
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == PermissionCode) {
            //假如允許了
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //do something
                Toast.makeText(this, "感謝賜予權限!", Toast.LENGTH_SHORT).show();
                startActivityForResult(new Intent(MainActivity.this, TakePicActivity.class), GetPhotoCode);
            }
            //假如拒絕了
            else {
                //do something
                Toast.makeText(this, "CAMERA權限FAIL", Toast.LENGTH_SHORT).show();
            }
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}



此時,你按相機,已經會出現畫面了吧!

但按下去的時候,他會頓一下(已經拍照ing)然後...就沒有然後了。

為什麼呢,其實已經有拍照的效果,只是沒有把畫面帶回MainActivity內而已
此時就要往takePicture這方法追到底發生什麼事情

仔細看一下作者寫的 CameraSurfaceView 會發現
 
 public void takePicture(){
        //设置参数,并拍照
        setCameraParams(mCamera, mScreenWidth, mScreenHeight);
        // 当调用camera.takePiture方法后,camera关闭了预览,这时需要调用startPreview()来重新开启预览
        mCamera.takePicture(null,null, jpeg);
    }

作者在這邊只拍了照,卻沒有結束掉finish();
這邊可以自己加入finish();

我們這邊設計,你按拍照後...
一、檢查相機權限。
二、帶畫面。(創建相片存放位置FilePath並且把filePath帶到拍照頁面的activity,拍完照後存入到filePath的位置)


上面已經增加了權限。
這邊我們來帶畫面吧
 在MainActivity先把要存照片的地方位置創建好。
 
 //創造檔案名稱、和存擋路徑
     String imageFilePath;

    private File createImageFile() throws IOException {
        String timeStamp =
                new SimpleDateFormat("yyyyMMdd_HHmmss",
                        Locale.getDefault()).format(new Date());
        String imageFileName = "IMG_" + timeStamp + "_";
        File storageDir =
                getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        File image = File.createTempFile(
                imageFileName,  /* prefix */
                ".jpg",         /* suffix */
                storageDir      /* directory */
        );


        imageFilePath = image.getAbsolutePath();
        return image;
    }

此時,我們就獲取到創建的File。


整理一下MainActivity

把權限和帶畫面的Code整理在同一個方法內

MainActivity
 
public class MainActivity extends AppCompatActivity {

    private Activity activity;
    public static final int PermissionCode = 1000;
    public static final int GetPhotoCode = 1001;

    private Button mBtnPic;
    private ImageView mShowImage;
    String imageFilePath;

    private boolean isCameraPermission = false;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        activity = this;
        initView();
        initListener();

    }

    private void initView() {
        mBtnPic = (Button) findViewById(R.id.btn_take_pic);
        mShowImage = (ImageView) findViewById(R.id.show_image);
    }

    private void initListener() {
        mBtnPic.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                openCamera();
            }
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == PermissionCode) {
            //假如允許了
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                isCameraPermission = true;
                //do something
                Toast.makeText(this, "感謝賜予權限!", Toast.LENGTH_SHORT).show();
                startActivityForResult(new Intent(MainActivity.this, TakePicActivity.class), GetPhotoCode);
            }
            //假如拒絕了
            else {
                isCameraPermission = false;
                //do something
                Toast.makeText(this, "CAMERA權限FAIL,請給權限", Toast.LENGTH_SHORT).show();
            }
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    //創造檔案名稱、和存擋路徑
    private File createImageFile() throws IOException {
        String timeStamp =
                new SimpleDateFormat("yyyyMMdd_HHmmss",
                        Locale.getDefault()).format(new Date());
        String imageFileName = "IMG_" + timeStamp + "_";
        File storageDir =
                getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        File image = File.createTempFile(
                imageFileName,  /* prefix */
                ".jpg",         /* suffix */
                storageDir      /* directory */
        );

        imageFilePath = image.getAbsolutePath();
        return image;
    }

    private void openCamera() {
        //已獲得權限
        if (isCameraPermission) {
            File photoFile = null;
            try {
                photoFile = createImageFile();
            } catch (IOException e) {
                Log.d("checkpoint", "error for createImageFile 創建路徑失敗");
            }
            //成功創建路徑的話
            if (photoFile != null) {
                Intent intent = new Intent(MainActivity.this, TakePicActivity.class);
                Bundle bundle = new Bundle();
                bundle.putString("url", photoFile.getAbsolutePath());
                intent.putExtras(bundle);
                startActivityForResult(intent, GetPhotoCode);
            }
        }
        //沒有獲得權限
        else {
            getPermission();
        }
    }

    private void getPermission() {
        //檢查是否取得權限
        final int permissionCheck = ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA);
        //沒有權限時
        if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
            isCameraPermission = false;
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.CAMERA,
                            Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    PermissionCode);
        } else { //已獲得權限
            isCameraPermission = true;
            openCamera();
        }
    }

}



再來就會動到原作者的Code了
CameraSurfaceView
 

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.widget.FrameLayout;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

/**
 * Created by Administrator on 2017/2/15 0015.//自定义相机
 */
public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Camera.AutoFocusCallback {

    private static final String TAG = "CameraSurfaceView";

    private Context mContext;
    private SurfaceHolder holder;
    private Camera mCamera;

    private int mScreenWidth;
    private int mScreenHeight;
    private CameraTopRectView topView;

    //更動
    private String filePath;
    private Activity activity;

    public CameraSurfaceView(Context context) {
        this(context, null);
    }

    public CameraSurfaceView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CameraSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        getScreenMetrix(context);
        topView = new CameraTopRectView(context, attrs);

        initView();


    }

    //拿到手机屏幕大小
    private void getScreenMetrix(Context context) {
        WindowManager WM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        WM.getDefaultDisplay().getMetrics(outMetrics);
        mScreenWidth = outMetrics.widthPixels;
        mScreenHeight = outMetrics.heightPixels;

    }

    private void initView() {
        holder = getHolder();//获得surfaceHolder引用
        holder.addCallback(this);
//        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//设置类型

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Log.i(TAG, "surfaceCreated");
        if (mCamera == null) {
            mCamera = Camera.open();//开启相机
            try {
                mCamera.setPreviewDisplay(holder);//摄像头画面显示在Surface上
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.i(TAG, "surfaceChanged");

        setCameraParams(mCamera, mScreenWidth, mScreenHeight);
        mCamera.startPreview();
//        mCamera.takePicture(null, null, jpeg);

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.i(TAG, "surfaceDestroyed");
        mCamera.stopPreview();//停止预览
        mCamera.release();//释放相机资源
        mCamera = null;
        holder = null;
    }

    @Override
    public void onAutoFocus(boolean success, Camera Camera) {
        if (success) {
            Log.i(TAG, "onAutoFocus success=" + success);
            System.out.println(success);
        }
    }


    private void setCameraParams(Camera camera, int width, int height) {
        Log.i(TAG, "setCameraParams  width=" + width + "  height=" + height);
        Camera.Parameters parameters = mCamera.getParameters();
        // 获取摄像头支持的PictureSize列表
        List pictureSizeList = parameters.getSupportedPictureSizes();
        for (Camera.Size size : pictureSizeList) {
            Log.i(TAG, "pictureSizeList size.width=" + size.width + "  size.height=" + size.height);
        }
        /**从列表中选取合适的分辨率*/
        Camera.Size picSize = getProperSize(pictureSizeList, ((float) height / width));
        if (null == picSize) {
            Log.i(TAG, "null == picSize");
            picSize = parameters.getPictureSize();
        }
        Log.i(TAG, "picSize.width=" + picSize.width + "  picSize.height=" + picSize.height);
        // 根据选出的PictureSize重新设置SurfaceView大小
        float w = picSize.width;
        float h = picSize.height;
        parameters.setPictureSize(picSize.width, picSize.height);
        this.setLayoutParams(new FrameLayout.LayoutParams((int) (height * (h / w)), height));

        // 获取摄像头支持的PreviewSize列表
        List previewSizeList = parameters.getSupportedPreviewSizes();

        for (Camera.Size size : previewSizeList) {
            Log.i(TAG, "previewSizeList size.width=" + size.width + "  size.height=" + size.height);
        }
        Camera.Size preSize = getProperSize(previewSizeList, ((float) height) / width);
        if (null != preSize) {
            Log.i(TAG, "preSize.width=" + preSize.width + "  preSize.height=" + preSize.height);
            parameters.setPreviewSize(preSize.width, preSize.height);
        }

        parameters.setJpegQuality(100); // 设置照片质量
        if (parameters.getSupportedFocusModes().contains(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
            parameters.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);// 连续对焦模式
        }

        mCamera.cancelAutoFocus();//自动对焦。
        mCamera.setDisplayOrientation(90);// 设置PreviewDisplay的方向,效果就是将捕获的画面旋转多少度显示
        mCamera.setParameters(parameters);

    }

    /**
     * 从列表中选取合适的分辨率
     * 默认w:h = 4:3
     * 注意:这里的w对应屏幕的height
     * h对应屏幕的width

*/
    private Camera.Size getProperSize(List pictureSizeList, float screenRatio) {
        Log.i(TAG, "screenRatio=" + screenRatio);
        Camera.Size result = null;
        for (Camera.Size size : pictureSizeList) {
            float currentRatio = ((float) size.width) / size.height;
            if (currentRatio - screenRatio == 0) {
                result = size;
                break;
            }
        }

        if (null == result) {
            for (Camera.Size size : pictureSizeList) {
                float curRatio = ((float) size.width) / size.height;
                if (curRatio == 4f / 3) {// 默认w:h = 4:3
                    result = size;
                    break;
                }
            }
        }

        return result;
    }


    // 拍照瞬间调用
    private Camera.ShutterCallback shutter = new Camera.ShutterCallback() {
        @Override
        public void onShutter() {
            Log.i(TAG, "shutter");
            System.out.println("执行了吗+1");
        }
    };

    // 获得没有压缩过的图片数据
    private Camera.PictureCallback raw = new Camera.PictureCallback() {

        @Override
        public void onPictureTaken(byte[] data, Camera Camera) {
            Log.i(TAG, "raw");
            System.out.println("执行了吗+2");
        }
    };

    //创建jpeg图片回调数据对象
    private Camera.PictureCallback jpeg = new Camera.PictureCallback() {

        private Bitmap bitmap;

        @Override
        public void onPictureTaken(byte[] data, Camera Camera) {


            topView.draw(new Canvas());

            BufferedOutputStream bos = null;
            Bitmap bm = null;
            if (data != null) {

            }

            try {
                // 获得图片
                bm = BitmapFactory.decodeByteArray(data, 0, data.length);
                Log.d("checkpoint", "checkpoint - " + bm);
//                if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
//                    String filePath = "/sdcard/dyk" + System.currentTimeMillis() + ".JPEG";//照片保存路径

//                    //图片存储前旋转
                Matrix m = new Matrix();
                int height = bm.getHeight();
                int width = bm.getWidth();
                m.setRotate(90);
                //旋转后的图片
                bitmap = Bitmap.createBitmap(bm, 0, 0, width, height, m, true);


                System.out.println("执行了吗+3");
                File file = new File(filePath);
                if (!file.exists()) {
                    file.createNewFile();
                }
                bos = new BufferedOutputStream(new FileOutputStream(file));

                Bitmap sizeBitmap = Bitmap.createScaledBitmap(bitmap,
                        topView.getViewWidth(), topView.getViewHeight(), true);
                bm = Bitmap.createBitmap(sizeBitmap, topView.getRectLeft(),
                        topView.getRectTop(),
                        topView.getRectRight() - topView.getRectLeft(),
                        topView.getRectBottom() - topView.getRectTop());// 截取


                bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);//将图片压缩到流中

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    bos.flush();//输出
                    bos.close();//关闭
                    bm.recycle();// 回收bitmap空间
                    mCamera.stopPreview();// 关闭预览
                    activity.setResult(Activity.RESULT_OK);
                    activity.finish();
//                    mCamera.startPreview();// 开启预览
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    };

    public void takePicture(Activity activity, String filePath) {
        this.filePath = filePath;
        this.activity = activity;
        //设置参数,并拍照
        setCameraParams(mCamera, mScreenWidth, mScreenHeight);
        // 当调用camera.takePiture方法后,camera关闭了预览,这时需要调用startPreview()来重新开启预览
        mCamera.takePicture(null, null, jpeg);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    }

}





我主要把activity和filePath帶入這邊的takePicture方法。
註解掉做者獲取filePath的方法,帶入自己的filePath。


並且增加activity關掉的finish()


再回到TakePicActivity,接收在MainActivity創建的filePath。
 
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;

public class TakePicActivity extends AppCompatActivity {


    private Button button;
    private CameraSurfaceView mCameraSurfaceView;

    private Activity activity;
    String filePath;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activity = this;
        getBundleData();

        initSet();
        initView();

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mCameraSurfaceView.takePicture(activity, filePath);
            }
        });
    }

    private void initSet() {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        // 全屏显示
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(R.layout.activity_take_pic);
    }


    private void initView() {
        mCameraSurfaceView = (CameraSurfaceView) findViewById(R.id.cameraSurfaceView);
        button = (Button) findViewById(R.id.takePic);
    }

    private void getBundleData() {
        Bundle bundle = getIntent().getExtras();
        if (bundle != null) {
            filePath = bundle.getString("url");
        }
        Log.d("checkpoint", "check filePath - " + filePath);
    }
}


再回到MainActivity做一次整理
 多增加onActivityResult 和 自寫的方法setPic
 
 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == GetPhotoCode) {
            setPic(imageFilePath);
        }
    }

    private void setPic(String mCurrentPhotoPath) {
        // Get the dimensions of the View
        int targetW = mShowImage.getWidth();
        int targetH = mShowImage.getHeight();

        // Get the dimensions of the bitmap
        BitmapFactory.Options bmOptions = new BitmapFactory.Options();
        bmOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
        int photoW = bmOptions.outWidth;
        int photoH = bmOptions.outHeight;

        // Determine how much to scale down the image
        int scaleFactor = Math.min(photoW / targetW, photoH / targetH);

        // Decode the image file into a Bitmap sized to fill the View
        bmOptions.inJustDecodeBounds = false;
        bmOptions.inSampleSize = scaleFactor;
        bmOptions.inPurgeable = true;

        Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
        mShowImage.setImageBitmap(bitmap);
    }



就完成囉!

有仔細看的朋友一定會想知道...

原作者filePath寫得好好的為啥要動?

拍照會延遲,可能照片還沒拍好、圖片都還沒存到路徑TackPicActivity就被關掉了。

所以我才改動filePath的取得方式。


屁話講了那麼多,重點大家就是要程式碼吧 哈哈哈

Demo:
https://github.com/nikeru8/CustomCamera


如果有錯誤或者不懂的地方歡迎提問!


文獻:
https://developer.android.com/training/camera/photobasics.html#TaskScalePhoto
 https://blog.csdn.net/LHBTM/article/details/55505668

2018年4月12日 星期四

Android方法 — 使用相機的正確姿勢(一)

前言:






這裡來實作相機的使用。

7.0牛扎糖官方改過權限,所以造成7.0以下會有FileUriExposedException的風險

這邊讓你迎刃而解















文獻來自官方檔案:
https://developer.android.com/training/camera/photobasics.html#TaskScalePhoto


實作:

先講一下要動到的地方




先在AndroidManifest.xml內給權限吧
 
<manifest>
...
      <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
...
</manifest>

在上述的程式中,android:required=”true”可以判斷使用者的手機裝置是否有相機。
假設我們改成false,我們就必須透過寫code的方式由我們自己去確認。

讓我們來開相機吧
處理開到系統相機Intent的部分
 
private static final int REQUEST_CAPTURE_IMAGE = 100;

private void openCameraIntent() {
    Intent pictureIntent = new Intent(
                              MediaStore.ACTION_IMAGE_CAPTURE
                           );
    if(pictureIntent.resolveActivity(getPackageManager()) != null) { 
           startActivityForResult(pictureIntent,
                              REQUEST_CAPTURE_IMAGE);
    }
}

我們再來處理拍完照後,回來App的畫面處理
 
@Override
protected void onActivityResult(int requestCode, int resultCode,
                                              Intent data) {
    if (requestCode == REQUEST_CAPTURE_IMAGE && 
                              resultCode == RESULT_OK) {
        if (data != null && data.getExtras() != null) {
        Bitmap imageBitmap = (Bitmap) data.getExtras().get("data");
        mImageView.setImageBitmap(imageBitmap);
        }
    }
}

簡單的拍照,返回照片就完成囉!!

下載Demo:https://github.com/nikeru8/CameraDemo/commits/master


但上面只是做好看的,因為並沒有『儲存』這個動作。

拿上面的程式碼拍完照後,會發現沒有在相簿裡出現!?

當然啊,你又沒寫XD

接下來你必須寫一個存入系統的方法

一樣先加入權限吧!
在manifests內加入存入權限
 
我以為要存儲權限,但原生的相機貌似不用。(而且這篇我也忘了加入呼叫權限的code XD


現在讓我們創建一個方法,創立文件名稱“日期_檔名”,把拍下來的照片寫入外部的檔案目錄內。
 
String imageFilePath;
private File createImageFile() throws IOException {
    String timeStamp = 
         new SimpleDateFormat("yyyyMMdd_HHmmss", 
                      Locale.getDefault()).format(new Date());
    String imageFileName = "IMG_" + timeStamp + "_";
    File storageDir =  
                getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    File image = File.createTempFile(
                    imageFileName,  /* prefix */
                    ".jpg",         /* suffix */
                    storageDir      /* directory */
    );

    imageFilePath = image.getAbsolutePath();
    return image;
}



上面的方法已經寫好了圖片儲存的地點,接下來開始存圖片了。
把創建資料夾的路徑帶給相機,並開啟系統資料夾!
下面複寫了剛剛上面開啟相機的方式
 
private static final int REQUEST_CAPTURE_IMAGE = 100;

private void openCameraIntent() {
        Intent pictureIntent = new Intent(
                MediaStore.ACTION_IMAGE_CAPTURE);
        if (pictureIntent.resolveActivity(getPackageManager()) != null) {
            //創建一個資料夾去存圖片
            File photoFile = null;
            try {
                photoFile = createImageFile();
            } catch (IOException ex) {
                //當創建資料夾失敗...
            }

            //當創建的資料夾不為null直,把創建資料夾的路徑帶給相機,並開啟系統資料夾
            if (photoFile != null) {
                Uri photoURI = FileProvider.getUriForFile(this, "com.example.android.provider", photoFile);
                pictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                        photoURI);
                startActivityForResult(pictureIntent,
                        REQUEST_CAPTURE_IMAGE);
            }
        }
    }


仔細看一下上述的程式碼,你一定會發現!“com.example.android.provider"是哪來的?在API等級 24以上的手機,都需要使用FileProvider去和你app內的manifest File 做連結。

讓我們開始FileProvider的步驟吧!
在manifest內加入provider 
 
<application>
   ...
   <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths">
        </meta-data>
    </provider>
    ...
</application>

發現${applicationId}.provider 了嗎?這個就是串聯剛剛的“com.example.android.provider"。

直接把 ${applicationId}改成你App的Id名稱,這邊拿我的專案當範例


這兩邊的名稱要是一樣的,之後會長這樣
android:authorities="com.hello.kaiser.startcamera.provider"

發現紅色的地方了嗎?
“@xml/file_paths”
這邊就要自己創建了。
資料夾創建在res下面
變成


之後創建名為file_paths的xml檔案
 
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images"
        path="Android/data/com.example.package.name/files/Pictures" />
</paths>

上述程式碼中的path對應的就是剛剛上面寫得code『getExternalFilesDir()』的部分,也是你存到指定環境Environment.DIRECTORY_PICTURES的地方。

所以path必須做更改。

同樣拿我的專案舉例子,path就會改成
path="Android/data/com.hello.kaiser.startcamera/files/Pictures" 
長這樣子。

就差不多完成囉!

此時,拍完照片 > 返回你的app,就剩下顯示影像的工作了。

是寫在onActivityResult的地方!
onActivityResult(int requestCode, int resultCode, Intent data)
但又跟我們一開始寫的方式不一樣,此時帶回來的data會為nulll,所以判斷onActivityResult的部分只需要判斷requestCode是否為我們剛剛帶入的REQUEST_CAPTURE_IMAGE就行了,剛剛在上面提過獲取了imageFilePath路徑的方法。

現在直接在onActivityReslut內調用
 
@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_CAPTURE_IMAGE && resultCode == RESULT_OK) {
            setPic();
        }
    }

    private void setPic() {
        // 獲取你在actiivity_layout地方的ImageView大小
        int targetW = mImageView.getWidth();
        int targetH = mImageView.getHeight();

        //獲取剛剛拍照圖片的大小
        BitmapFactory.Options bmOptions = new BitmapFactory.Options();
        bmOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(imageFilePath, bmOptions);
        int photoW = bmOptions.outWidth;
        int photoH = bmOptions.outHeight;

        //修改你要顯示圖片的尺寸大小
        int scaleFactor = Math.min(photoW / targetW, photoH / targetH);

        //使用Bitmap size去調整ImageView內顯示的圖片
        bmOptions.inJustDecodeBounds = false;
        bmOptions.inSampleSize = scaleFactor;
        bmOptions.inPurgeable = true;
        Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath, bmOptions);

        //顯影像
        mImageView.setImageBitmap(bitmap);
    }

可以開啟你的程式,試著拍照吧。
會存在手機裡面囉!

在onActivityReslut內可以使用更簡潔的方式,使用Glide

直接
 
Glide.with(this).load(imageFilePath).into(mImageView);

Glide介紹及使用方式:
http://nikeru8.blogspot.tw/2017/03/third-party-frescopicasso.html
官方:
https://github.com/bumptech/glide

需要注意的事情是,目前的第三方套件,套用方式已經改變
請在你的Gradle內加入
 
repositories {
  mavenCentral()
  google()
}

dependencies {
  implementation 'com.github.bumptech.glide:glide:4.7.1'
  annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'
}

這是在android3.0之後的改變

有問題請提出吧。

你的問題會是我創作很大的動力!

Demo:https://github.com/nikeru8/CameraDemo


心得:

之後還寫了一篇
客製化相機(二) 關於證件照的。

請參閱囉



2018年4月11日 星期三

Android方法(過濾Log) — Filter content in ERRORLOG

前言:


新一點版本的手機會發現,Log會跳出很多不大相干的訊息,大多都是手機底層自己的Log。

低階一點的手機就沒這問題。


過濾:


不囉唆了

^(?!.*(AAA)).*$

就是長這樣
中間的AAA就是你要過濾的訊息。




使用:

接下來看圖說故事




在上圖中,如果你想過濾TAG為OpenGLRenderer的Log
^(?!.*(OpenGLRenderer)).*$


那如果我想過濾複數的Log怎麼辦?

那就用『 | 』這個符號吧
^(?!.*(OpenGLRenderer|eglCodecCommon|zygote)).*$

上面這樣可以同時過濾這三種TAG的Log


就可以把Log內容用得像以前一樣乾淨囉!



總結:

這次也沒啥好總結的,就上面那麼簡單,突然想到一件事情想分享一下。

今天的總結突然想講一下徐曉東MMA狂人去年大戰太極和永春高手的事情。
http://news.ltn.com.tw/news/world/breakingnews/2075960

我和徐曉冬的想法其實是一致的, 在這年代中國武術就真的是打養身的,你學了太極或是永春,說穿了就只是能打贏一般沒練家子的人而已吧。

 這讓我想到萬維綱的專欄中 曾這樣講過:
因為這是一個充分競爭,充分交流的時代。
只有競爭不充分的領域才有“風格”,比如我們看拳擊比賽、格鬥散打比賽,兩個選手的打法都非常相似 — 為什麼不像電影裡面一樣,一個用鐵砂掌、一個用螳螂拳呢?

 原因很明顯了,競爭交流不夠充分。

 如果武術門派之間充分交流,像現在職業足球這樣每週打一場聯賽,那麼不同打法之間很快就能分出高下,然後所有門派都會去學習最高級的那種打法。那麼最後結果就是所有人將都使用同樣的打法打比賽。

上述講的,也許就是今天MMA的打法。

再拿足球來說,三十年前足球是一個有明顯不同風格的運動,比如英格蘭愛打長傳衝釣,南美洲足球則主打腳法細膩,有時候可以看到一個人帶著球就可以過好幾個人,還有“歐洲拉丁派”。
但是今天我們看世界盃, 這些風格都不那麼明顯了。巴西隊也常追求快速推進,英格蘭也不打長傳衝釣了。當然你可以說某些人的打法就是與眾不同!但關鍵字是,不明顯

十幾年前圍棋大師也很講究『風格』。什麼流什麼流的,招數就那麼多,大家從小就學過,無非就是靈活運用,哪裡還談得上流派。


這就是充分競爭和交流的結果。

把這結果再來想想現在的手機,十年前的手機,各家品牌都有明顯的各自風格,外觀千奇百怪,有滑蓋式的、有折疊式的...應有盡有、百花齊放,而今天的手機正面全都是一塊黑屏。

拿現代MMA去和中國武術比,大概就是拿現在Iphone去和Nokia8810做比較一樣,同樣都是武功,一般人來打是肯定打得贏的,但都是練家子,中國武術練的就是Nokia8810,MMA練的就是Iphone。



2017年12月15日 星期五

Android更新 3.0 —(問題、為什麼要更新3.0?)

前言:

最近要分享的東西有點多,遇到的東西太多了反而沒時間寫blog

最有感的必須先寫!太重要。

很多人說android studio 3.0 或是3.0.1很多bug

是沒錯啦 畢竟新版本,而且更新上去後介面會有點不太習慣,Gradle要稍微做改動。

但帶來的好處實在太多了。

這邊講一個最大的好處。

就是build專案的速度大幅度的提升!


我的app很肥大,如果gradle的設定不用offonline的話,平均花費的時間大概三分半。

扯吧....

就算開了offonline build專案也一分半。搞得寫程式常常寫到分神XD (這一分半一定會想玩玩手機滑滑臉書什麼的)

Android 3.0 更新後build專案

各位觀眾

只要十幾秒!!!

 一定要更新啊各位

其他好處就不贅述了 https://developer.android.com/studio/index.html


更新會遇到的問題

請參照 https://litotom.com/2017/10/29/as3-gradle/

2017年11月28日 星期二

Android元件(TabLayout + ViewPager)– FragmentPagerAdapter簡易使用(二) 、TabLayout指示器

前言

除了ViewPager頁面的轉換,現在上面大多都還會有一個Tab。
這邊使用我之前的DEMO 直接加上TabLayout
有兩篇可以先參考一下
 FragmentPagerAdapter簡易使用(一)
http://nikeru8.blogspot.tw/2017/11/androidfragmentpageradapterviewpager.html
還有
簡單ViewPager頁面 banner自動擴充實現
http://nikeru8.blogspot.tw/2017/11/androidviewpager-viewpager-banner.html

上面兩個demo都可以直接配合這次的TabLayout完成!
 如圖

今日實作


程式碼

First –

看圖說故事。
然後打上design
看到他後把他加入Gradle內,就開始使用囉




畫面xml拉好後
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.hello.kaiser.fragment.MainActivity">

    <android.support.design.widget.TabLayout
        android:id="@+id/view_tab"
        android:layout_width="match_parent"
        android:layout_height="44dp" />

    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/view_tab"
        android:background="@android:color/darker_gray" />

</RelativeLayout>
 



設定一下元件 findViewById
    private TabLayout mTab;
    mTab = (TabLayout) findViewById(R.id.view_tab);
 

讓我們來加入Tab吧
 mTab.addTab(mTab.newTab().setText("第一頁"));
 mTab.addTab(mTab.newTab().setText("第二頁"));
 

這就可以在上方產生兩個Tab

然後要讓ViewPager滑動時,上方Tab跟著滑動
 mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTab));
 

除了滑動之外
點擊Tab,下方的ViewPager當然也要有所動作
mTab.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(mViewPager));
 

最簡單的使用方式就完成囉

如果你還想要再按下Tab時候,有額外的事件發生

可以設定她的監聽Listener
mTab.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {

            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {

            }
        });
 

簡單的方式大致上就這樣

這邊再講一個新增Tab的方法

可以在xml直接上TabItem


<android.support.design.widget.TabLayout
        android:id="@+id/view_tab"
        android:layout_width="match_parent"
        android:layout_height="44dp">

        <android.support.design.widget.TabItem
            android:id="@+id/one"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="one" />

        <android.support.design.widget.TabItem
            android:id="@+id/two"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="two" />

    </android.support.design.widget.TabLayout>
 
和ViewPager連動的部分運用上面的code在做連動

app:tabIndicatorColor :指示條的顏色
app:tabIndicatorHeight :指示條的高度
app:tabSelectedTextColor : tab被選中時的字體顏色
app:tabTextColor : tab未被選中時的字體顏色
app:tabMode="scrollable" : 默認是fixed:固定的,標籤很多時候會被擠壓,不能滑動。


 增加Tab的部分,這邊介紹了兩種,個人覺得使用代碼的方式呈現比較好,因為後台如果要新增Tab,這樣又要再xml上新增TabItem。
如果使用代碼呈現,可以用for迴圈讓Tab的數量隨著後台的要求成長!



可能發生的問題

在普通的手機上都很正常,如果你在平板上造正常步驟設置妳的TabLayout
會有跑版問題,字體會往中間集中,下方那條指示器indicator也會變得很短。

在我百思不得其解下Rick大神解決了我的困難,在此歌頌一下師傅。

在xml內設置
  <android.support.design.widget.TabLayout
       ...
        app:tabGravity="fill"
        app:tabMaxWidth="0dp"
       ...
        />
 
建議在使用TabLayout時都把這兩個加上,在平板上就不會跑版了。
感恩Seafood!讚嘆Seafood!



DEMO
https://github.com/nikeru8/FragmentPagerAdapterDemo/tree/addTabLayout

文獻:
http://www.jianshu.com/p/be1e8a1da639
http://givemepass-blog.logdown.com/posts/288943-how-to-use-tablayout

2017年11月27日 星期一

Android元件(FragmentPagerAdapter、ViewPager)– FragmentPagerAdapter簡易使用(一)

前言

之前寫了一個簡單的ViewPager範本。

簡單ViewPager頁面 banner自動擴充實現 
 http://nikeru8.blogspot.tw/2017/11/androidviewpager-viewpager-banner.html

 這篇偏向Banner

現在寫的FragmentPagerAdapter偏向頁面的使用方式


如下圖





重點程式碼

 step one — FragmentPagerAdapter建構
先來看Adapter的部分 FragmentPagerAdapter

需要有幾個方法要複寫
 //必要建構子
    public adapter(FragmentManager fm) {
        super(fm);
    }

    //你想要顯示的Fragment
    @Override
    public Fragment getItem(int position) {
        return null;
    }

    //要顯示的筆數
    @Override
    public int getCount() {
        return 0;
    }
 

step two – Fragment

再來就是Fragment的部分
在開始前,先來看一下Fragment的生命週期

在這邊我們可以複寫onCreate、onCreateView

並創建newInstance

newInstance可以自動創建
public static FragmentSingle newInstance() {
        //這邊設計呼叫Fragment的方式,Bundle內可以從activity > adapter 帶參數過來
        Bundle args = new Bundle();
        FragmentSingle fragment = new FragmentSingle();
        fragment.setArguments(args);
        return fragment;
    }

 
newInstance使用Bundle接參數帶入fragment。
在onCreate的地方真正的把參數帶到fragment內

 //繼承fragment後,直接打newInstance會自動生成此方法,裡面都幫你建置好Bundle類了
    public static CreateFragment newInstance(int showType, String content) {
        //Bundle接收傳過來的參數
        Bundle args = new Bundle();
        args.putInt("showType", showType);
        args.putString("content", content);
        CreateFragment fragment = new CreateFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //帶入參數
        showType = getArguments().getInt("showType");
        content = getArguments().getString("content");
    }
 

接下來還要複寫兩種方法
onCreateView 和 onViewCreated

onCreateView:用來創建Layout xml的,你想創建什麼型態的xml也可以設定在這裡,有在上方圖片的生命週期內

onViewCreated:用來取得參數,理所當然的這個方法就會在onCreateView之後,使用getView()這個方法取得xml內的id

這邊舉個onViewCreated的使用
 
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    TextView message = getView().findViewById(R.id.item_one);
    message.setText(content);
}


大致上這樣。

這邊說明一下比較妥當的使用時機。

我是希望程式碼已不攏長,可讀性為主,所以可以的話,讓頁面長的雷同的地方再使用會比較好。

舉個例子:



像是這個App這頁面,有著高相似性的頁面,使用這方法就會是上上之選。

STEP THREE –

Fragment寫好後可以把它塞入Adapter內了

public class CustomFragmentAdapter extends FragmentPagerAdapter {

    String mTitleOne, mTitleTwo;

    public CustomFragmentAdapter(FragmentManager fm) {
        super(fm);
    }

    public void setDataOne(String messgae) {
        //這邊你可以在activity內獲取資料,再利用getItem傳入CreateFragment
        mTitleOne = messgae;
        notifyDataSetChanged();
    }

    public void setDataTwo(String message) {
        //這邊你可以在activity內獲取資料,再利用getItem傳入CreateFragment
        mTitleTwo = message;
        notifyDataSetChanged();
    }

    @Override
    public Fragment getItem(int position) {
        //創作每一個Fragment內的內容
        if (position == 0) {//第一頁
            return new CreateFragment().newInstance(position, mTitleOne);
        } else {//第二頁
            return new CreateFragment().newInstance(position, mTitleTwo);
        }
    }

    @Override
    public int getCount() {
        //你得Fragment頁面數
        return 2;
    }
}
 

說明我寫在方法的上方。
此時你會看到我多寫了兩個方法setDataOne和setDataTwo
如果你兩個頁面的內容相差很多,要帶入不同的參數進去,可以使用這個方法和Activity要資料,在getItem內new不同的Fragment把資料帶進去。

當然你也可以直接寫在Adapter建構子內直接取值,但如果之後需要刷新fragment內的資料就要重新new一個Adapter,不是很聰明的作法。


完整程式碼

今日會動到的地方



簡單一個ViewPager
activity_main.xml
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.hello.kaiser.fragment.MainActivity">

    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/darker_gray" />

</RelativeLayout>
 
create_fragment_one.xml
create_fragment_one.xml
你
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:background="@color/colorPrimary"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/item_one"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="one"
        android:textSize="40sp" />
</LinearLayout>
 

create_fragment_two.xml
create_fragment_two.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:background="@color/colorAccent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/item_two"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="two"
        android:textSize="40sp" />
</LinearLayout>
 

畫面畫好了 來看程式碼吧

CustomFragmentAdapter.java
public class CustomFragmentAdapter extends FragmentPagerAdapter {

    String mTitleOne, mTitleTwo;

    public CustomFragmentAdapter(FragmentManager fm) {
        super(fm);
    }

    public void setDataOne(String messgae) {
        //這邊你可以在activity內獲取資料,再利用getItem傳入CreateFragment
        mTitleOne = messgae;
        notifyDataSetChanged();
    }

    public void setDataTwo(String message) {
        //這邊你可以在activity內獲取資料,再利用getItem傳入CreateFragment
        mTitleTwo = message;
        notifyDataSetChanged();
    }

    @Override
    public Fragment getItem(int position) {
        //創作每一個Fragment內的內容
        if (position == 0) {//第一頁
            return new CreateFragment().newInstance(position, mTitleOne);
        } else {//第二頁
            return new CreateFragment().newInstance(position, mTitleTwo);
        }
    }

    @Override
    public int getCount() {
        //你得Fragment頁面數
        return 2;
    }
}

 

CreateFragment.java
public class CreateFragment extends Fragment {

    private int showType;
    private String content;
    private Context context;

    //繼承fragment後,直接打newInstance會自動生成此方法,裡面都幫你建置好Bundle類了
    public static CreateFragment newInstance(int showType, String content) {
        //Bundle接收傳過來的參數
        Bundle args = new Bundle();
        args.putInt("showType", showType);
        args.putString("content", content);
        CreateFragment fragment = new CreateFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //帶入參數
        showType = getArguments().getInt("showType");
        content = getArguments().getString("content");
    }


    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.d("checkpoint", "onCreateView onCreateView");
        if ("0".equals(String.valueOf(showType))) {//第一頁
            return LayoutInflater.from(context).inflate(R.layout.create_fragment_one, null);
        } else if ("1".equals(String.valueOf(showType))) {//第二頁
            return LayoutInflater.from(context).inflate(R.layout.create_fragment_two, null);
        } else {
            return null;
        }
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        Log.d("checkpoint", "checkonViewCreatedpoint onViewCreated");

        super.onViewCreated(view, savedInstanceState);
        if (showType == 0) {//第一頁
            TextView message = getView().findViewById(R.id.item_one);
            message.setText(content);

        } else if (showType == 1) {//第二頁
            TextView message = getView().findViewById(R.id.item_two);
            message.setText(content);
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        this.context = context;
    }
}

 

MainActivity.java
public class MainActivity extends AppCompatActivity {

    private ViewPager mViewPager;
    private CustomFragmentAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initSet();
    }

    private void initView() {
        mViewPager = (ViewPager) findViewById(R.id.view_pager);
    }

    private void initSet() {
        mAdapter = new CustomFragmentAdapter(getSupportFragmentManager());
        mViewPager.setAdapter(mAdapter);
        mAdapter.setDataOne("你好第一頁");
        mAdapter.setDataTwo("歡迎光靈第二頁 hello ");
    }
}

 


DEMO
https://github.com/nikeru8/FragmentPagerAdapterDemo/tree/master



歡迎交流喔!



Fragment全集
 

Fragment的使用(一) ─ 在activity內放置Fragment
Fragment的使用(二) ─ 在activity內切換Fragment

Fragment 返回上一頁 - OnBackPressed
Fragment點擊穿透
FragmentPagerAdapter

2017年11月26日 星期日

Android元件(AsyncTask)— 非同步任務、異步任務

前言

現在下載東西大多都使用第三方套件,異步任務都已經幫你處理得妥妥的。
但如果你要生成一張很大的圖片或是很好資源的任何東西,你就會需要它

這邊貼個介紹
http://aiur3908.blogspot.tw/2015/06/android-asynctask.html
超好懂的AsyncTask

以下簡略轉貼
求詳細請點上面網址

今日實作


重點程式碼

AsyncTask<Params, Progress, Result>



你開的class 會需要繼承 AsyncTask

而後面的三個參數

Params  代表著你要放入的參數,像是String 或是 int 

Progress 如果你要下載東西,這邊專門給你用進度條的參數

Result 就是你希望這條你新分出去的執行緒要回到主執行緒的時候要返回什麼東西。
如果你是拿來下載圖片,就可以返回Bitma。如果你是跑一個陣列,也可以返回ArrayList<>之類的....


而繼承AsyncTask會產生四個方法

onPreExecute  執行前,一些基本設定可以在這邊做。

doInBackground  執行中,在背景做任務。這邊就是重點中的重點,你希望下載或做事情的方法寫在這邊

onProgressUpdate  執行中,專門給Porgress條做監聽的方法

onPostExecute  執行後,最後的結果會在這邊。如果你Result的部分設定返回圖片,這裡就會噴Bitmap給你

方法會長以下這樣 

private class GetImage extends AsyncTask<String , Integer , Bitmap>{

        @Override
        protected void onPreExecute() {
            //執行前 設定可以在這邊設定
            super.onPreExecute();
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            //執行中 在背景做事情
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            //執行中 可以在這邊告知使用者進度
            super.onProgressUpdate(values);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            //執行後 完成背景任務
            super.onPostExecute(bitmap);
        }
}
 

String... 和 int... 的部分可以參考我寫的一篇猛虎出閘
http://nikeru8.blogspot.tw/2017/08/java.html

如果是複數的Params,使用方式就是Params[0]、Params[1]依此類推...

Params[0] 就會是helloOne
Params[1] 就是 helloTwo

使用範例

private class GetImage extends AsyncTask<String , Integer , Bitmap>{

        private ProgressDialog progressBar;
        //進度條元件

        @Override
        protected void onPreExecute() {
            //執行前 設定可以在這邊設定
            super.onPreExecute();

            progressBar = new ProgressDialog(MainActivity.this);
            progressBar.setMessage("Loading...");
            progressBar.setCancelable(false);
            progressBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            progressBar.show();
            //初始化進度條並設定樣式及顯示的資訊。
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            //執行中 在背景做事情
            int progress = 0;
            for (String urlStr : params) {
                try {
                    URL url = new URL(urlStr);
                    Bitmap bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream());
                } catch (Exception  e) {
                    e.printStackTrace();
                }
                publishProgress(progress+=33);
                //有三張圖 每張圖33%
            }
            publishProgress(100);
            //最後達到100%
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            //執行中 可以在這邊告知使用者進度
            super.onProgressUpdate(values);
            progressBar.setProgress(values[0]);
            //取得更新的進度
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            //執行後 完成背景任務
            super.onPostExecute(bitmap);

            progressBar.dismiss();
            //當完成的時候,把進度條消失
            imageView.setImageBitmap(bitmap);
        }
    }
 


完整程式碼

main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:clickable="true"
        android:gravity="center"
        android:onClick="onbuttonclick"
        android:text="產生QRCode" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/button"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="45dp" />
</RelativeLayout>
 

MainActivity.java
public class MainActivity extends AppCompatActivity {


    private Activity activity;

    private ProgressDialog loadingDialog;

    //要生成的QRCode內容
    String QRCodeContent = "http://nikeru8.blogspot.tw/2017/04/third-partyxzingcore-qrcodejar.html";
    ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        activity = this;
        //參數
        imageView = (ImageView) findViewById(R.id.imageView);

        //設定loading Dialog
        loadingDialog = new ProgressDialog(activity);
        loadingDialog.setCanceledOnTouchOutside(false);
        loadingDialog.setCancelable(true);
        loadingDialog.setMessage("loading please wait...");
    }

    public void onbuttonclick(View view) {
        new GetImage().execute(QRCodeContent);
    }


    private class GetImage extends AsyncTask<String, Integer, Bitmap> {

        @Override
        protected void onPreExecute() {
            //執行前 設定可以在這邊設定

            loadingDialog.show();
            super.onPreExecute();
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            //執行中 在背景做事情
            int QRCodeWidth = 600;
            int QRCodeHeight = 600;
            //QRCode內容編碼
            Map hints = new EnumMap(EncodeHintType.class);
            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");

            MultiFormatWriter writer = new MultiFormatWriter();
            try {
                //ErrorCorrectionLevel容錯率分四級:L(7%) M(15%) Q(25%) H(30%)
                hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);

                //建立QRCode的資料矩陣
                BitMatrix result = writer.encode(params[0], BarcodeFormat.QR_CODE, QRCodeWidth, QRCodeHeight, hints);

                //建立矩陣圖
                Bitmap bitmap = Bitmap.createBitmap(QRCodeWidth, QRCodeHeight, Bitmap.Config.ARGB_4444);
                for (int y = 0; y < QRCodeHeight; y++) {
                    for (int x = 0; x < QRCodeWidth; x++) {
                        bitmap.setPixel(x, y, result.get(x, y) ? Color.BLACK : Color.WHITE);
                    }
                }

                return bitmap;

            } catch (WriterException e) {
                e.printStackTrace();
                return null;
            }

        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            //執行中 可以在這邊告知使用者進度
            super.onProgressUpdate(values);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            //執行後 完成背景任務
            super.onPostExecute(bitmap);
            imageView.setImageBitmap(bitmap);
            loadingDialog.dismiss();
        }
    }
}

 


Demo


我拿QRCode寫個範例

因為QRCode如果跑太大張,通常都會造成畫面卡頓

使用AsyncTask就可以完美解決這問題

https://github.com/nikeru8/QRCodeDemo

協程(coroutine) - 協程為什麼要學它?

 Coroutine 協程 再強調一次 協程就是由kotlin官方所提供的線程api //Thread Thread { }.start() //Executor val execu...