show code block

顯示具有 Android元件 標籤的文章。 顯示所有文章
顯示具有 Android元件 標籤的文章。 顯示所有文章

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

2017年11月22日 星期三

Android元件(Indicator) – 指示器Indicator,ViewPager下方的圓點

前言


目前市面上已經很多Banner的第三方可以使用了
甚至是已經套好的ViewPager第三方 和Banner第三方。



這邊提供一個我自己常用的Indicator第三方
https://github.com/romandanylyk/PageIndicatorView

可以下載我的ViewPager demo 自己套套看這個第三方

情況是這樣的:
前幾天我使用Fragment,發現如果同時創建兩個相同的Fragment但不同內容的頁面,這個套件的Indicator會指向同一個id

 簡而言之就是兩個Banner會共用到同一個Indicator


只好自己寫一個Indicator來用
ViewPager Banner 內的指示器




本日實作


先下載ViewPagerDemo我直接在這篇加內容


首先呢 先下載制作Indicator的圖片
分別是
圖片一

圖片二

有看到東西嗎?

沒看到沒關係,因為我也沒看到XD  本來就是白色的

在這講一下,一個是空心圓 一個是實心圓

下載的地方幫你擺在這 點我下載


改動的部分
綠色為新增的

一、先把你剛剛下載的圖片放到drawable內

此時你會注意看到圖片內有一個xml檔案叫indicator_selector.xml


這其實可以自己創

每個item後面就是這張圖片被按到後的狀態
state_selected是被選取的狀態,true 是被選到,false則相反
看上圖,可以看出當true(被選取時),是實心圓,false(沒被選取)則是空心圓

這種寫法同樣可以套用在按鈕上,使用TextView設計一個按鈕,按下時的和非按下時會顯示不一樣的圖。詳情見:http://nikeru8.blogspot.tw/2017/03/textview.html


二、創建attrs.xml



命名為 attrs

attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="indicatorIcon" format="reference" />
    <attr name="indicatorMargin" format="dimension" />
    <attr name="indicatorSmooth" format="boolean" />
    <attr name="indicatorSize" format="dimension" />

    <declare-styleable name="IndicatorView">
        <!--指示器樣式-->
        <attr name="indicatorIcon" />
        <!--指示器的間距-->
        <attr name="indicatorMargin" />
        <!--指示器是否顯示滑動效果-->
        <attr name="indicatorSmooth" />
        <!--指示器的大小-->
        <attr name="indicatorSize" />
    </declare-styleable>

</resources> 

這裡是幹嘛的呢?
讓我們把畫面轉到activity_main.xml稍微更改了一下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:custom="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="250dp">

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


        <com.hello.kaiser.viewpager.IndicatorView
            android:id="@+id/id_view_indicator"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_gravity="center_horizontal|bottom"
            android:layout_marginBottom="32dp"
            app:indicatorSmooth="false"
            custom:indicatorIcon="@drawable/indicator_selector"
            custom:indicatorMargin="5dp"
            custom:indicatorSize="9dp" />
    </RelativeLayout>
</RelativeLayout>
 

此時你會發現 在最外層的RelativeLayout 內
多了一個  xmlns:custom="http://schemas.android.com/apk/res-auto"
然後再看看新多的IndicatorView
是不是有剛剛在attrs內新增的參數了,indicatorIconindicatorMargin ...這類的

那要如何使用它呢?

創一個IndicatorView.java
public class IndicatorView extends View implements ViewPager.OnPageChangeListener{

    private Drawable mIndicator; //指示器的显示样式
    private int mIndicatorSize; //指示器的大小
    private boolean mSmooth; //是否显示滑动效果
    private int mMargin;  //指示器之间的间距

    private int mDefaultMargin; //默认的间距
    private int mSelectedItem; //选中的指示器

    private int mWidth; //测量后得到的控件的宽度
    private int mCount; //viewpager的页数,即指示器的个数
    private int mContextWidth; //实际计算得到的控件的宽度
    private float mOffset; //滑动偏移量


    private ViewPager.OnPageChangeListener mPageChangeListener;

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

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

    public IndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context,attrs,defStyleAttr);
    }

    private void init(Context context, AttributeSet attrs, int defStyleAttr) {

        //设置指示器大小的默认值
        mIndicatorSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,16,getResources().getDisplayMetrics());
        //设置指示器间距的默认值
        mDefaultMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,5,getResources().getDisplayMetrics());

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndicatorView,defStyleAttr,0);

        mIndicator = typedArray.getDrawable(R.styleable.IndicatorView_indicatorIcon);
        mMargin = (int) typedArray.getDimension(R.styleable.IndicatorView_indicatorMargin,mDefaultMargin);
        mSmooth = typedArray.getBoolean(R.styleable.IndicatorView_indicatorSmooth,true);
        mIndicatorSize = (int) typedArray.getDimension(R.styleable.IndicatorView_indicatorSize,mIndicatorSize);

        typedArray.recycle();

        mIndicator.setBounds(0,0,mIndicatorSize,mIndicatorSize);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
    }

    /**
     * 测量宽
     * @param widthMeasureSpec
     * @return
     */
    private int measureWidth(int widthMeasureSpec) {
        final int mode = MeasureSpec.getMode(widthMeasureSpec);
        final int size = MeasureSpec.getSize(widthMeasureSpec);

        int width;
        //计算整个控件的宽度,其中 mCount 是获取到的ViewPager的页数
        int desired = getPaddingLeft() + mIndicatorSize * mCount + mMargin * (mCount - 1) + getPaddingRight();
        mContextWidth = desired;

        if (mode == MeasureSpec.EXACTLY) {
            //如果是match_parent,则选取屏幕宽度和计算得到的宽度中较大的那个作为控件的宽度
            width = Math.max(desired,size);
        }else {
            if (mode == MeasureSpec.AT_MOST) {
                //如果是wrap_parent,则选取屏幕宽度和计算得到的宽度中较小的那个作为控件的宽度
                width = Math.min(desired,size);
            }else {
                //还有一种情况是 UNSPECIFIED ,一般是系统内部测量的过程中使用,这种情况也可以忽略掉
                width = desired;
            }
        }

        mWidth = width;
        return width;
    }

    /**
     * 测量高
     * @param heightMeasureSpec
     * @return
     */
    private int measureHeight(int heightMeasureSpec) {
        final int mode = MeasureSpec.getMode(heightMeasureSpec);
        final int size = MeasureSpec.getSize(heightMeasureSpec);

        int height;
        int desired = getPaddingTop() + mIndicatorSize + getPaddingBottom();

        if (mode == MeasureSpec.EXACTLY) {
            height = size;
        }else {
            if (mode == MeasureSpec.AT_MOST) {
                height = Math.min(desired,size);
            }else {
                height = desired;
            }
        }

        return height;
    }

    @Override
    protected void onDraw(Canvas canvas) {

        canvas.save();
        /**
         * 这里是计算绘制控件的x轴上的起点位置
         * 当wrap_content并且控件宽度小于屏幕宽度时,计算得到的宽度和实际宽度相同,mWidth/2 - mContextWidth/2 = 0,这种情况很好理解
         * 再看当match_content的情况,控件的计算宽度小于屏幕宽度,则mWidth > mContextWidth,则计算得到的left是计算得到的控件距离屏幕左端的距离,
         * 控件将绘制在屏幕水平方向的中间,不清楚可以在纸上画下,其他情况也类似
         */
        int left = mWidth/2 - mContextWidth/2 + getPaddingLeft();
        canvas.translate(left,getPaddingTop());
        //依次绘制指示器
        for (int i = 0 ; i < mCount ; i++) {
            mIndicator.setState(EMPTY_STATE_SET);
            mIndicator.draw(canvas);
            canvas.translate(mIndicatorSize + mMargin,0);
        }
        canvas.restore();

        /**
         * 下面是绘制选中的指示器的样式
         */
        float leftDraw = ( mIndicatorSize + mMargin ) * ( mSelectedItem + mOffset );
        canvas.translate(left,getPaddingTop());
        canvas.translate(leftDraw,0);
        mIndicator.setState(SELECTED_STATE_SET);
        mIndicator.draw(canvas);

    }

    public void setViewPager(ViewPager viewPager) {
        if (viewPager == null) {
            return;
        }
        PagerAdapter pagerAdapter = viewPager.getAdapter();
        if (pagerAdapter == null) {
            throw new RuntimeException("请先设置viewpager的adaper");
        }
        mCount = pagerAdapter.getCount();
        viewPager.addOnPageChangeListener(this);
        mSelectedItem = viewPager.getCurrentItem();
        invalidate();
    }

    /**
     * 因为自定义控件注册了viewpager的滑动监听,
     * 为了外部能够同样监听到滑动事件,所以提供该接口
     * @param pageChangeListener
     */
    public void setOnPageChangeListener(ViewPager.OnPageChangeListener pageChangeListener) {
        this.mPageChangeListener = pageChangeListener;
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if (mSmooth){
            //允许显示滑动效果
            mSelectedItem = position;
            mOffset = positionOffset;
            //通知view重绘
            invalidate();
        }
        if (mPageChangeListener != null) {
            mPageChangeListener.onPageScrolled(position,positionOffset,positionOffsetPixels);
        }
    }

    @Override
    public void onPageSelected(int position) {
        //当有page被选中后通知view重绘
        mSelectedItem = position ;
        invalidate();

        if(mPageChangeListener != null){
            mPageChangeListener.onPageSelected(position);
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        if(mPageChangeListener != null){
            mPageChangeListener.onPageScrollStateChanged(state);
        }
    }
}

 

使用方式

這時候就可以在activity_main.xml內使用Indicator
 <com.hello.kaiser.viewpager.IndicatorView
            android:id="@+id/id_view_indicator"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_gravity="center_horizontal|bottom"
            android:layout_marginBottom="32dp"
            app:indicatorSmooth="false"
            custom:indicatorIcon="@drawable/indicator_selector"
            custom:indicatorMargin="5dp"
            custom:indicatorSize="9dp" />
 
這裡要注意的事,如果你是用複製貼上的方式在看這篇BLOG
你會發現會報錯,因為你寫的IndicatorView位置和我的並不一樣
我寫在com.hello.kaiser.viewpager.IndicatorView內,你必須指定你自己的IndicatorView才有辦法使用

在MainActivity.java內,設定IndicatorView的ViewPager吧
 IndicatorView indicatorView = (IndicatorView) findViewById(R.id.id_view_indicator);
 indicatorView.setViewPager(mViewPager);
 



DEMO
https://github.com/nikeru8/ViewPagerDemo/tree/AddIndicator




Android元件(ViewPager)– 簡單ViewPager頁面 banner自動擴充實現

前言

ViewPagerRecyclerView大概是開發者最常用到的兩個元件了。
最近會圍繞著這兩個主題講一下Adapter的設計。

其實之前講過一篇,其實以前的網誌提過。

看看以前的網誌,才能深刻的感受到原來成長就是這麼一回事XD
今天來講一下ViewPager實現。

你塞給他多少圖片,他會自行擴充的ViewPager

今日實作ViewPager



 重點程式碼

ViewPager和RecyclerView的共同點,重點都是Adapter
 //製作Adapter
    class BannerAdapter extends PagerAdapter {

        private LayoutInflater mInflater;
        private List<String> mUrlList;

        //建構子
        public BannerAdapter(Context context, List<String> mUrlList) {
            //如果把這個寫在instantiateItem的話,每一個Item都會呼叫一次,很吃資源
            mInflater = LayoutInflater.from(context);
            this.mUrlList = mUrlList;
        }

        //看你這ViewPager要有幾頁,靠著List的大小擴充
        @Override
        public int getCount() {
            //如果陣列為空,返回0 防呆機制
            return mUrlList == null ? 0 : mUrlList.size();
        }

        //用來判斷目前的畫面是否和instantiateItem創建的為同一個
        @Override
        public boolean isViewFromObject(View view, Object o) {
            return o == view;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            //每一個Item都創建View
            View view = mInflater.inflate(R.layout.main_viewpager, container, false);
            ImageView imageView = (ImageView) view.findViewById(R.id.image);

            //用Glide下載圖片
            Glide.with(mContext)
                    .load(mUrlList.get(position))
                    .error(R.drawable.image_loading)
                    .placeholder(R.drawable.image_loading)
                    .centerCrop()
                    .fitCenter()
                    .diskCacheStrategy(DiskCacheStrategy.RESULT)
                    .into(imageView);
            container.addView(view);
            return view;
        }

        //移除ViewPager內所對應的視圖
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }
    }
 
這邊是我所寫的Adapter
其中下載圖片的方式請參考 Glide 

重點複寫override四個方法

getCount   要顯示的頁數數量

isViewFromObject  判斷目前的畫面是否和instantiateItem創建的為同一個


instantiateItem  Item畫面的製作 有點像RecyclerView內的 onCreateViewHolder + onBindViewHolder


destroyItem  移除ViewPager內所對應的視圖



全部程式碼


今日會碰觸到的地方

drawable內的 image_loading 請自行去找一張你愛的loading圖,網路上很多可自己google或是看下方demo從那邊下載


先在AndroidManifest.xml內配置網路權限
 
<uses-permission android:name="android.permission.INTERNET">
 

然後再build.gradle內配置好下載圖片的Glide
compile 'com.github.bumptech.glide:glide:3.7.0'
 
設定好了讓我們做正事吧!

先來畫
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.viewpager.MainActivity">

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

</RelativeLayout>

簡單的塞了一個ViewPager

再來我們的每一個頁面ViewPager需要顯示一張圖片
main_viewpager.xml

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

    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:scaleType="fitXY" />
</RelativeLayout>
 

MainActivity.java
public class MainActivity extends AppCompatActivity {

    private Context mContext;

    //參數
    private ViewPager mViewPager;

    //假資料

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
        mViewPager = (ViewPager) findViewById(R.id.view_pager);

        //假資料
        List<String> mUrlList = new ArrayList<>();
        mUrlList.add("http://android.suvenconsultants.com/newimage/android-developer2.png");
        mUrlList.add("http://cdn.makeuseof.com/wp-content/uploads/2017/05/android-apps-eat-battery-670x335.jpg");
        mUrlList.add("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSpSWJ32XK7TcLUT2XpXTrzwHMOiKHJkzD7QodGrhRzfvRn35Z4mg");
        mUrlList.add("http://android.suvenconsultants.com/newimage/android-developer2.png");

        //呼叫Adapter
        BannerAdapter bannerAdapter = new BannerAdapter(this, mUrlList);
        mViewPager.setAdapter(bannerAdapter);

    }


    //製作Adapter
    class BannerAdapter extends PagerAdapter {

        private LayoutInflater mInflater;
        private List<String> mUrlList;

        //建構子
        public BannerAdapter(Context context, List<String> mUrlList) {
            //如果把這個寫在instantiateItem的話,每一個Item都會呼叫一次,很吃資源
            mInflater = LayoutInflater.from(context);
            this.mUrlList = mUrlList;
        }

        //看你這ViewPager要有幾頁,靠著List的大小擴充
        @Override
        public int getCount() {
            //如果陣列為空,返回0 防呆機制
            return mUrlList == null ? 0 : mUrlList.size();
        }

        //用來判斷目前的畫面是否和instantiateItem創建的為同一個
        @Override
        public boolean isViewFromObject(View view, Object o) {
            return o == view;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            //每一個Item都創建View
            View view = mInflater.inflate(R.layout.main_viewpager, container, false);
            ImageView imageView = (ImageView) view.findViewById(R.id.image);

            //用Glide下載圖片
            Glide.with(mContext)
                    .load(mUrlList.get(position))
                    .error(R.drawable.image_loading)
                    .placeholder(R.drawable.image_loading)
                    .centerCrop()
                    .fitCenter()
                    .diskCacheStrategy(DiskCacheStrategy.RESULT)
                    .into(imageView);
            container.addView(view);
            return view;
        }

        //移除ViewPager內所對應的視圖
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }
    }

}

 


需要注意的幾個地方


飄移
在寫demo時,發現我的viewPager會漂移...

檢查了一下發現我犯了幾個錯誤, 在main_viewpager.xml內的ImageView的圖片必須是固定大小。

在做專案時,必須先和美工溝通好,出圖必須一樣大小
或是在這邊把圖片的大小(width 和 height)先行寫死

<ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:scaleType="fitXY" />

android:layout_centerInParent="true" 必須有這個固定住ImageView在中間
ViewPager通常都是固定高度的,如果你需要隨著app的大小做高度伸展,可以使用code自行修改,這邊不再贅述




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


2017年10月17日 星期二

Android方法(BuildType、Flavors、variants) — 簡易使用Flavors不同開發環境

前言:


如果你們公司需要不同的開發環境來做App測試,可以試試看這個。
flavors and buildType

這個的效果是什麼呢?

可以讓你把測試環境給分開


有什麼特別的嗎?

當然有特別,這三個App其實是同一個。
那為什麼要設成三個呢?
可以拿來當切換環境用,一個開發團隊內通常會有測試區lab和正式區。
 
如果在做檢查的時候,正式區和測試區切來切去非常的不方便。
你的程式大概會長這樣


每次切換都要重新選擇註解,再build一次。
中間等待的時間就白白浪費了,所以可以使用flavors來同時build起不同的開發環境。

這邊指讓你最簡易程度上的完成實作。
當然還有其他的玩法,詳情請見其他地方 or flavors and buildType



重點程式碼:

再gradle內

 

 defaultConfig {
       ...
        resValue "string", "app_label", "default_name"
        buildConfigField "String", "initAPI", "defult_value"
    }

productFlavors {
        dev {
            applicationId "com.example.branchOne"
            versionName "MyApp_dev"
            resValue "string","app_name","DEV_APP"
            buildConfigField "String", "initAPI", "\"https://www.google.com.tw/\""
        }
        RC {
            applicationId "com.example.branchTwo"
            versionName "MyApp_RC"
            resValue "string", "app_name", "RC_APP"
            buildConfigField "String", "initAPI", "\"開發環境一\""
        }
        PUB {
            applicationId "com.example.branchThree"
            versionName "My App"
            resValue "string","app_name","PUB_APP"
            buildConfigField "String", "initAPI", "\"正式版\""
        }
    }
 

上面的initAPI參數都應該是你們自己的api網址,在引號內的\"不能少

介紹一下裡面在幹嘛的
applicationId 每個app要獨立出來都必須有自己的androidId,如果androidId一樣系統會判定你為同一個APP

resValue 這是指會在下圖的地方看到dev pub rc 你設定的文字都會跑到這裡



再來是
buildConfig 看下圖,她會出現在和resValue很近的地方



再buildConfig內,程式碼會自動生成
public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String APPLICATION_ID = "com.example.branchTwo";
  public static final String BUILD_TYPE = "debug";
  public static final String FLAVOR = "RC";
  public static final int VERSION_CODE = 1;
  public static final String VERSION_NAME = "MyApp_RC";
  // Fields from product flavor: RC
  public static final String initAPI = "開發環境一";
}

 

你會發現它自動生成了 initAPI,此時開發環境一就會是一串網址。

如何使用initAPI呢?
在要call這串網址的地方BuildConfig.initAPI就行了。
因為我們有在gradle > defaultConfig內設定初始值
如果你在productFlavors內沒有設定buildConfigField "String", "initAPI", "正式版"
會自動幫妳生成defult_value這個string




使用方式:


把畫面帶到android studio的左下角 有一個build Variants 在這裡切換你想要build的版本就行了。
當然他有分bugrelease版本
release版本有牽扯到 發布問題的key
單純demo就不說了。

然後如果你在程式內有寫DB,會需要做額外設定。

詳情請見
https://stackoverflow.com/questions/14368867/permission-denial-opening-provider

如果我有用到的話再做討論XD

有問題歡迎提出。

2017年10月6日 星期五

Android元件(ActivityLifecycleCallbacks) — 逾時登出製作 (二)

前言:

伊隆·馬斯克 space x 的創辦人,曾經說過把事情簡化的重要性。

像是把一件不可能的事情,中間的步驟不斷分解簡化。
就像他提出的第一性原理
就是讓我們把目光從那些別人做的事上挪開,不管做什麼選擇,都以最根本的那個原則為參照點,不斷用那個參照點為自己糾偏。

 上一次做逾時登出也是用這方法,把每一個想得到的情況都包含進去,在不斷簡化。

但在簡化的過程,其實也可以抬頭看一下有沒有其他平行更好的方法。

剛剛我發現了一個更快的方式,一個方法就概括監控所有Activity的方法
 ActivityLifecycleCallbacks
 https://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks.html

 ActivityLifecycleCallbacks

他能瞬間監控所有的activity,也能在其中做一些變化,像是ToolBar
關於ToolBar我是參考
http://www.jianshu.com/p/75a5c24174b2

這篇讓你實現每個頁面實現toolbar的方式。

有興趣可以連過去看看,這邊只講逾時登出。



 重點程式碼:

Application內 可以直接使用ActivityLifecycleCallbacks
public class MyApplicationTwo extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle bundle) {
                
            }

            @Override
            public void onActivityStarted(Activity activity) {

            }

            @Override
            public void onActivityResumed(Activity activity) {

            }

            @Override
            public void onActivityPaused(Activity activity) {

            }

            @Override
            public void onActivityStopped(Activity activity) {

            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {

            }

            @Override
            public void onActivityDestroyed(Activity activity) {

            }
        });
    }
}

 

上述就和Activity的生命週期幾乎一模一樣,在每個方法內打上Log。

設置兩三個Activity就可以做測試了!

在逾時登出的案子裡,我們還原一下裡面的CustomApplication這個方法
http://nikeru8.blogspot.tw/2017/09/android.html

把整個App關於逾時登出的部分全部砍掉,包含情境A情境B


這裡一行就完成ActivityLifecycleCallbacks
public class CustomApplication extends Application {

    private static final String TAG = "CustomApplication";
    private static long PAUSE_TIME = 0;//暫停的時間點
    private static long RESTART_TIME = 0;//喚醒的時間點

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle bundle) {
                Log.d(TAG, "onActivityCreated");
            }

            @Override
            public void onActivityStarted(Activity activity) {
                Log.d(TAG, "onActivityStarted");
            }

            @Override
            public void onActivityResumed(Activity activity) {
                Log.d(TAG, "onActivityResumed");
                if (PAUSE_TIME != 0) {
                    RESTART_TIME = System.currentTimeMillis();
                    long totalTime = RESTART_TIME - PAUSE_TIME;
                    Log.d("checkpoint", "checkpoint PAUSE_TIME = " + PAUSE_TIME);
                    Log.d("checkpoint", "checkpoint RESTART_TIME = " + RESTART_TIME);
                    Log.d("checkpoint", "checkpoint = " + totalTime);
                }

            }

            @Override
            public void onActivityPaused(Activity activity) {
                Log.d(TAG, "onActivityPaused");
            }

            @Override
            public void onActivityStopped(Activity activity) {
                Log.d(TAG, "onActivityStopped");
            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
                Log.d(TAG, "onActivitySaveInstanceState");
            }

            @Override
            public void onActivityDestroyed(Activity activity) {
                Log.d(TAG, "onActivityDestroyed");
            }
        });
    }
}

 
這就和Activity的生命週期一模一樣。
我在喚醒activity的Resume部分,寫了方法。

假如停止時間點有值,喚醒時,拿PAUSE_TIME(暫停的時間點)比較一下 RESTART_TIME (喚醒的時間點)
long 每一千個單位 等於 一秒。
相減下來如果1204,就等於app暫停了1.204



好啦,試試看自己的App能不能有逾時登出的效果吧!
以下是我的Log
checkpoint 就是相扣的單位 2934
所以是2.934


2017年9月24日 星期日

Android元件(WebView) — webView延伸,開啟FaceBook直播

前言:

之前發生一個問題,如果直接用WebView開啟Facebook的直播會出現一片空白的問題。

 因為要看直播一定要開啟facebook的頁面,他會要求你們開啟FB原生的app看直播



但點擊「開啟facebook」後連過去會是一片空白。
稍微檢查一下點連擊的url就會發現問題所在了。

他吐給你的url是一串意義不明的東西。


intent://video/?id=%7B25668421....

大致上長上面那樣

這時候我查了一下開啟fb的方式

發現他們開啟fb的暗碼都是 fb://xxxx

shouldOverrideUrlLoading撈到url後,直接把url內的intent改成fb就可以順利開啟直播囉!



重點程式碼:

下面例子的facebook直播網址請去臉書上面隨機找一個直播連結。

 WebViewClient mWebViewClient = new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Log.d(TAG, "checkpoint  = " + url);

            if (url.contains("com.facebook.katana")) {
                try {
                    //確認是否有安裝facebook,假如這串錯誤 直接跳到catch
                    activity.getPackageManager().getPackageInfo("com.facebook.katana", 0);
                    //如果有抓取到url 把吐出來的intent改成fb
                    String facebookUrl = url.replace("intent", "fb");
                    //在外部開啟連結
                    startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(facebookUrl)));
                    //在Log確認開啟連結的網址
                    Log.d("MainActivity", "facebookUrl = " + facebookUrl);
                } catch (PackageManager.NameNotFoundException e) {
                    Toast.makeText(activity, "你沒安裝facebook喔,請先安裝!", Toast.LENGTH_SHORT).show();
                }
                finish();
            } else {
                view.loadUrl(url);
            }

            return true;
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
        }

        @Override
        public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
            super.onReceivedError(view, request, error);
            Log.d(TAG, "error onReceivedError = " + request);
            view.setVisibility(View.GONE);
        }
    };
 

重點都在這了,下面就直接上完整程式碼。



完整程式碼:

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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.webview.MainActivity">

    <WebView
        android:id="@+id/webview_show"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</android.support.constraint.ConstraintLayout>

 

MainActivity.java
下面的直播連結請去fb隨機找一個直播來取代
public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();

    private WebView mWebView;
    private Activity activity;
    public static String FACEBOOK_URL = "https://www.facebook.com/hua.yingxin.9/videos/256684214854665/?hc_ref=ARTnenvMQbNXRjjvZag73twoaqxWfAd3a8D-U11DAVIaopXBFDEcHXMoGsG4KYL7BCQ&fref=gs&dti=1461364913895987&hc_location=group";


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

        mWebView = (WebView) findViewById(R.id.webview_show);
        WebSettings websettings = mWebView.getSettings();
        websettings.setSupportZoom(true);
        websettings.setJavaScriptEnabled(true);
        websettings.setAppCacheEnabled(true);
        websettings.setSaveFormData(true);
        websettings.setAllowFileAccess(true);
        websettings.setDomStorageEnabled(true);
        websettings.setBuiltInZoomControls(true);
        websettings.setDisplayZoomControls(false);
//        String ua = mWebView.getSettings().getUserAgentString();
//        mWebView.getSettings().setUserAgentString("WebView");

        websettings.setLayoutAlgorithm(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ? WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
                : WebSettings.LayoutAlgorithm.NARROW_COLUMNS);
        websettings.setLoadWithOverviewMode(true);
        websettings.setUseWideViewPort(true);

        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
            websettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
        }

        mWebView.setWebViewClient(mWebViewClient);
//        mWebView.setWebViewClient(new WebViewClient());
        mWebView.setWebChromeClient(new WebChromeClient());

        mWebView.loadUrl(FACEBOOK_URL);

    }

    WebViewClient mWebViewClient = new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Log.d(TAG, "checkpoint  = " + url);

            if (url.contains("com.facebook.katana")) {
                try {
                    //確認是否有安裝facebook,假如這串錯誤 直接跳到catch
                    activity.getPackageManager().getPackageInfo("com.facebook.katana", 0);
                    //如果有抓取到url 把吐出來的intent改成fb
                    String facebookUrl = url.replace("intent", "fb");
                    //在外部開啟連結
                    startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(facebookUrl)));
                    //在Log確認開啟連結的網址
                    Log.d("MainActivity", "facebookUrl = " + facebookUrl);
                } catch (PackageManager.NameNotFoundException e) {
                    Toast.makeText(activity, "你沒安裝facebook喔,請先安裝!", Toast.LENGTH_SHORT).show();
                }
                finish();
            } else {
                view.loadUrl(url);
            }

            return true;
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
        }

        @Override
        public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
            super.onReceivedError(view, request, error);
            Log.d(TAG, "error onReceivedError = " + request);
            view.setVisibility(View.GONE);
        }
    };
}
 



DEMO:

https://github.com/nikeru8/webviewConvertToFb






2017年9月19日 星期二

Android元件(ProgressBar) — ProgressBar進度條

前言:

製作ProgressBar

其實之前在

Android元件(DownloadManager) ─ downloadManager下載元件

就有使用過,這次把他拉出來另外做一篇。




重點程式碼:

先來看一下官方文檔ProgressBar

你要使用進度條,一定要知道三件事情。

一、你最大100%的比例為何(setMax)

二、現在跑到的比例在哪了(setProgress)

三、進度條的長相是水平的,還是圓形的.... 等等 (Style)
這邊是所有的Style,參考文檔可以去看看
Other progress bar styles provided by the system include:




實作:

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.progressbardemo.MainActivity">

    <LinearLayout
        android:id="@+id/bar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/progress_bar"
            style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_weight="9" />

        <TextView
            android:id="@+id/progress_tv"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="5dp"
            android:layout_weight="1"
            android:text="0%" />

    </LinearLayout>

    <Button
        android:onClick="increasing"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/bar_layout"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="40dp"
        android:text="增加10" />


</RelativeLayout>

 

我在activity_main.xml內多設置了一個按鈕來模擬增加的比率和多增一個TextView來觀測%數。

MainActivity.java
 
public class MainActivity extends AppCompatActivity {


    private ProgressBar mProgressBar;
    private TextView mPercentage;

    private int progress = 0;//變化參數

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

        initView();
        initSet();

    }

    private void initView() {
        mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
        mPercentage = (TextView) findViewById(R.id.progress_tv);
    }

    private void initSet() {
        mProgressBar.setMax(100);
    }


    public void increasing(View view) {
        progress += 10;
        if (progress <= 100)
            mPercentage.setText(progress + "%");
        else
            Toast.makeText(this, "已經跑完囉", Toast.LENGTH_SHORT).show();

        mProgressBar.setProgress(progress);
    }
}




DEMO:

https://github.com/nikeru8/ProgressBarDemo

久違的github
為什麼之前不用呢?  這不好說XD

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

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