show code block

2018年12月9日 星期日

Java基礎:for and foreach(加強版迴圈)

前言:

最近接到一個案子
處理資料比較重,邏輯上還必須使用到雙迴圈。
但大概才3000多筆資料而已
在雙迴圈+大量資料的情況下,用普通的for迴圈一秒居然才跑四筆....
這真的很讓人無言
原本有嘗試使用多執行緒去分割資料,分開讀取。
但效果還是和預想的差很多,畢竟一秒只跑四筆資料

最後我找到的解決方式 foreach

拿三千筆數量下去計算:
普通for要跑750s
foreach 1s結束

處理起來普通版for迴圈跟樹懶一樣讓人無言

實作:
List StringList = new ArrayList<>();
//用普通版迴圈 創建資料
for (int i = 0; i < 10000; i++) {
    StringList.add("比對資料 - " + i);
}

//普通版迴圈
for (int i = 0; i < StringList.size(); i++) {
    Log.d("CHECKPOINT", "check i " + StringList.get(i));
}

//Foreach 加強版回圈
for (String item : StringList) {
    Log.d("CHECKPOINT", "check i " + item);
}


為什麼會比較快呢?

普通for迴圈就是照著開始設定的 i 一個一個下去跑

foreach是用『遍歷』的方式下去跑

差在哪呢?

何謂遍歷?

一張圖了解差別
foreach遍歷
遍歷
普通for迴圈



結論:


當處理的資料量很大很大時,foreach超級強!!

資料量小,很容易就跑完,反而普通版的迴圈效能會比較好

可以想像成
你想運算的東西如果是工廠
普通版For就是一條生產線
Foreach是一間工廠多開很多條生產線
如果你運算量很小,你還要多花效能去多開生產線,告訴工人要做什麼,很不划算。
反而拖累整間工廠的運作


補充:
如果要再foreach遍歷的迴圈內計算count

可以使用這種方式
//Foreach 加強版回圈
int count = 0;
for (String item : StringList) {
    Log.d("CHECKPOINT", "check i " + item + "count - " + count);
    count++;
}





2018年11月14日 星期三

Java基礎:抽象abstract 和 介面interface




抽象方法abstract 和 介面interface

直接進入正題

abstract可以解釋成“一定會有的東西,所以只能繼承一次。
interface可以解釋成“附屬品”,可有可無,但有大量的東西都需要它,他就派上用場了。

舉實際的例子:武器
武器都一定有攻擊力、攻擊範圍、名字

這時候如果你需要大量的使用到武器,那上述說的『攻擊力、攻擊範圍、名字』這些東西就適合放在abstract。
但武器百百種,可能長槍會有紅櫻、刀和劍會有劍柄而且也可能會有紅櫻、長弓、短弓和十字弓會有
這些東西就不是一定會存在武器內的東西,我們就可以使用interface




實作:
先把傷害、攻擊範圍、武器名稱寫成abstract
武器.java

abstract public class 武器 {
    abstract int damage();

    abstract int range();

    abstract String getName();
}
這樣武器的基底,就出現了。
來製作武器吧
先來做一把霸王槍
霸王槍.java

public class 霸王槍 extends 武器 {

    @Override
    int damage() {
        return 200;
    }

    @Override
    int range() {
        return 80;
    }

    @Override
    String getName() {
        return getClass().getSimpleName();
    }

}

一把霸王槍就誕生了,他同時擁有了傷害、攻擊範圍、和名稱
再來創建一把青龍偃月刀配戴紅櫻

武器紅櫻.java
interface 紅纓 {

    int 裝配紅櫻();
}

然後是青龍偃月刀.java
public class 青龍偃月刀 extends 武器 implements 紅櫻 {
    @Override
    public int damage() {
        return 250;
    }

    @Override
    protected int range() {
        return 90;
    }

    @Override
    protected String getName() {
        return getClass().getSimpleName();
    }

    @Override
    public String 紅櫻樣式() {
        return "大花樣,關羽專用";
    }
}

這樣就區分了青龍偃月刀和霸王槍之間的差別。
再來
每把長槍,希望青龍偃月刀比較不同,那就給他一個顏色吧
武器顏色.java
public interface 武器配色 {
    String 顏色();
}

在改動青龍偃月刀
public class 青龍偃月刀 extends 武器 implements 紅櫻,武器配色 {
    @Override
    public int damage() {
        return 250;
    }

    @Override
    protected int range() {
        return 90;
    }

    @Override
    protected String getName() {
        return getClass().getSimpleName();
    }

    @Override
    public String 紅櫻樣式() {
        return "大花樣,關羽專用";
    }

    @Override
    public String 顏色() {
        return "青龍色";
    }
}


有注意到了嗎?
武器的是每一把、每一種武器都能套用的,但介面的部分可以一直增加。

此時我在創造一把武器,干將劍

我要有刀柄和紅櫻
干將劍.java
public class 干將劍 extends 武器 implements 武器刀柄,紅櫻{
    @Override
    public int damage() {
        return 170;
    }

    @Override
    protected int range() {
        return 10;
    }

    @Override
    protected String getName() {
        return getClass().getSimpleName();
    }

    @Override
    public int 刀柄長度() {
        return 5;
    }

    @Override
    public String 紅櫻樣式() {
        return "春秋古代款";
    }
}


一把佩戴紅纓的春秋款干將就誕生了

大致上abstract 和 interface的用法就是這樣

在寫個人使用他們吧:
我招喚出
張無忌.java
public class 張無忌 {

    private 武器 weapon;

    public void attack() {
        if (weapon == null) {
            System.out.println("張無忌空手,使用拳頭,造成敵人十點傷害");
            return;
        }
        System.out.println("張無忌使用 " + weapon.getName() + ",造成敵人" + weapon.damage() + "傷害");
    }

    public void 換武器(武器 weapon) {
        this.weapon = weapon;
    }

}


之後再MainActivity.java內來操作。
MainActivity.java
public class MainActivity extends AppCompatActivity {

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

        張無忌 person = new 張無忌();

        person.attack();

        武器 weapon = new 干將劍();
        person.換武器(weapon);
        person.attack();
    }

}





2018年6月28日 星期四

Anroid元件(LocalBroadcastManager) — fragment刷新

前言:



fragment刷新對很多人來說是個坑。

不管fragment刷新 fragment
還是fragment 刷新 activity
或是activity 刷新 fragment

都可以用,萬用

有人應該會有疑問,這不是廣播嗎?
如果我參數設的跟別人一樣,不就會喚起我這個app了?

但仔細看他的元件名稱前方有個Local,應該就不擔心了吧。他只在你app內運作。
當你銷毀fragment 或是 activity時,註銷它就可以了。

開始之前如果對FragmentPagerAdapter、ViewPager不熟,可以先參考一下這篇。
http://nikeru8.blogspot.com/2017/11/androidfragmentpageradapterviewpager.html


重點程式碼:


 //參數
    protected LocalBroadcastManager broadcastManager;


 //接收廣播 (信件 然後你要 do something)
    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(final Context context, Intent intent) {
            // 想像成從遠方寄來的信,可以是任何東西
            String action = intent.getStringExtra("changeSomething");
            if ("changeText".equals(action)) {
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        //do something
                        text_change.setText("你按了我~!!我刷新囉!");
                    }
                });
            }
        }
    };

    //註冊廣播 (你家的信箱)
    private void registerReceiver() {
        broadcastManager = LocalBroadcastManager.getInstance(getActivity());
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("changeFragment");
        broadcastManager.registerReceiver(mReceiver, intentFilter);
    }


    //呼叫刷新畫面(寄件人)
    private void refreshData() {
        //指定要刷新的頁面給intent (
        Intent intent = new Intent("changeFragment");
        //要帶過去的參數
        intent.putExtra("changeSomething", "changeText");
        LocalBroadcastManager.getInstance(activity).sendBroadcast(intent);
    }


     //離開fragment後,消滅廣播 (搬家後,信箱地址換掉)
     @Override
    public void onDetach() {
        super.onDetach();
        broadcastManager.unregisterReceiver(mReceiver);
    }

    //註冊廣播 (蓋成房子後,買一個信箱) 
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        registerReceiver();
    }


 

這邊解釋一下,可以想像成fragment是一棟房子,你要寄信過去。
房子-fragment
信箱-registerReceiver()
寄件人-refreshData()
信件-mReceiver

有了房子(fragment)就必須有信箱(registerReceiver())才能收信,所以要在房子內蓋一個信箱。

當寄件人(refreshData())寄出了信,就會投地到信箱(registerReceiver()),然後你可以開啟信件(mReceiver)收到任何東西、做任何事。

夠簡單吧。


就不全部貼出來了,直接看代碼吧。


github:https://github.com/nikeru8/refreshFragment



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
把上面這篇全部搬到你個人的專案內。

會動到的地方

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

比較需要注意的,就是xmlView

記得換成自己包名的位置


記得更換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);

    }

}





我主要把activityfilePath帶入這邊的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對應的就是剛剛上面寫得codegetExternalFilesDir()』的部分,也是你存到指定環境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就是你要過濾的訊息。




使用:

接下來看圖說故事




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


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

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

上面這樣可以同時過濾這三種TAGLog


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



總結:

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

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

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

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

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

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

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

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

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


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

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

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



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

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