Xây dựng ứng dụng chụp ảnh và filter camera trên Android (p2)

Hello, mình là Dương Vũ. Đây là bài viết thứ 2 trong loạt bài hướng dẫn xây dựng ứng dụng chụp ảnh và filter camera trên Android. Ở bài viết trước mình đã hướng dẫn xây dựng màn hình Splash có chức năng hiện ra dòng giới thiệu về ứng dụng, đồng thời xin cấp các permission sử dụng trong ứng dụng. Bài này, ta sẽ cùng nhau xây dựng module chính của ứng dụng: module camera và chụp ảnh. Sau khi hoàn thành bài viết này, ứng dụng của ta sẽ có thêm các chức năng:

  • Camera kèm theo hiệu ứng (filter)
  • Hiển thị danh sách filter để lựa chọn khi chụp ảnh
  • Lưu trữ ảnh trên bộ nhớ thiết bị
  • Các chức năng cơ bản của camera: quay trước, quay sau, on/off chế độ chụp flash.

Nào, bắt đầu nhé!

[toc]

Màn hình Main

Ok, mình sẽ sử dụng luôn lớp MainActivity mà IDE tạo ra cho  lúc khởi tạo dự án để viết chức năng camera. Màn hình này bao gồm 1 camera view có kích thước chiều rộng full màn hình, chiều cao bằng chiều rộng (tỉ lệ 1:1). Có 1 button để chụp ảnh, 1 recyclerView để hiển thị danh sách hiệu ứng, các button chức năng của camera (on/off flash, đổi camera). Ngoài ra màn hình còn có thêm 1 button cho phép chọn ảnh từ gallery. Ok, ta hãy bắt tay vào xây dựng layout cho nó.

Xây dựng layout

Ta chỉnh sửa lại file activity_main.xml như sau:

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

    <RelativeLayout
        android:id="@+id/tb_camera"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:background="@color/toolbar_color">

        <ImageView
            android:id="@+id/bt_close"
            android:layout_width="@dimen/_30sdp"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="@dimen/_4sdp"
            android:adjustViewBounds="true"
            android:padding="@dimen/_8sdp"
            android:src="@drawable/ic_close_camera" />

        <ImageView
            android:layout_width="@dimen/_30sdp"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginLeft="@dimen/_4sdp"
            android:layout_marginRight="@dimen/_4sdp"
            android:adjustViewBounds="true"
            android:padding="@dimen/_6sdp"
            android:src="@drawable/ic_done_camera" />

        <TextView
            style="@style/UTM_AvoBold"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="CAMERA"
            android:textColor="#3c3837"
            android:textSize="14sp" />

    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/rl_camera_area"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/tb_camera">

        <org.wysaid.view.CameraRecordGLSurfaceView
            android:id="@+id/c_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <SeekBar
            android:id="@+id/seekBar"
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:layout_alignParentTop="true"
            android:layout_gravity="center_horizontal|top"
            android:visibility="gone" />

        <ImageView
            android:id="@+id/bt_rotate_camera"
            android:layout_width="@dimen/_40sdp"
            android:layout_height="@dimen/_40sdp"
            android:layout_alignParentBottom="true"
            android:padding="@dimen/_10sdp"
            android:src="@drawable/ic_rotate_camera" />

        <ImageView
            android:id="@+id/bt_flash_mode"
            android:layout_width="@dimen/_40sdp"
            android:layout_height="@dimen/_40sdp"
            android:layout_alignParentBottom="true"
            android:layout_alignParentRight="true"
            android:padding="@dimen/_10sdp"
            android:src="@drawable/ic_turn_on_flash" />

        <ImageView
            android:layout_width="@dimen/_55sdp"
            android:layout_height="@dimen/_55sdp"
            android:layout_centerInParent="true"
            android:src="@drawable/ic_focus_camera" />

    </RelativeLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_filter"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="@color/toolbar_color">

    </android.support.v7.widget.RecyclerView>

    <RelativeLayout
        android:id="@+id/rl_camera_action"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/rv_filter"
        android:background="@color/toolbar_color">

        <com.makeramen.roundedimageview.RoundedImageView xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/iv_pick_image"
            android:layout_width="@dimen/_40sdp"
            android:layout_height="@dimen/_40sdp"
            android:layout_centerVertical="true"
            android:layout_margin="@dimen/_10sdp"
            android:scaleType="fitXY"
            android:src="@drawable/default_avatar"
            app:riv_border_color="@android:color/white"
            app:riv_border_width="3dp"
            app:riv_corner_radius="@dimen/_5sdp"
            app:riv_mutate_background="true"
            app:riv_oval="false" />

        <ImageView
            android:id="@+id/bt_take_picture"
            android:layout_width="@dimen/_50sdp"
            android:layout_height="@dimen/_50sdp"
            android:layout_centerInParent="true"
            android:layout_margin="@dimen/_10sdp"
            android:scaleType="fitXY"
            android:src="@drawable/ic_take_picture" />

    </RelativeLayout>

</RelativeLayout>

Bạn đừng lo lắng khi nhìn sang preview thấy layout của ta như 1 mớ shit. Ta sẽ chỉnh sửa lại kích thước cho chúng ở phần code Java. Ở đây, ta chỉ cần lưu ý tới 1 view đặc biệt, đó là org.wysaid.view.CameraRecordGLSurfaceView. Đây chính là view thuôc thư viện xử lý ảnh mà mình đã trình bày ở bài đầu tiên. Nó sẽ có nhiệm vụ hiển thị camera preview cho chúng ta.

MainActivity.java

Khai báo layout đã xong, giờ ta sẽ chuyển tới phần Java code. Việc đầu tiên là ta sẽ bắt MainActivity kế thừa BaseActivity ta đã tạo ra (chứ không phải kế thừa AppCompatActivity như mặc định), sau đó sẽ là bindView cho nó:

package fbphoto.thou.com.myapplication;

import android.support.v7.widget.RecyclerView;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.makeramen.roundedimageview.RoundedImageView;
import org.wysaid.view.CameraRecordGLSurfaceView;
import butterknife.Bind;

/**
 * Created by Computer on 2/12/2018.
 */

public class MainActivity extends BaseActivity {

    @Bind(R.id.rl_camera_area)
    RelativeLayout rlCameraView;
    @Bind(R.id.c_view)
    CameraRecordGLSurfaceView cameraView;
    @Bind(R.id.bt_flash_mode)
    ImageView btFlashMode;
    @Bind(R.id.rv_filter)
    RecyclerView rvFilter;
    @Bind(R.id.iv_pick_image)
    RoundedImageView ivPickImage;

    @Override
    protected int getLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected void createView() {

    }
}

Setup camera

Tiếp theo, mình sẽ viết 1 phương thức setUpCameraView, phương thức này sẽ chỉnh sửa lại kích thước của cameraView, do ở phần layout xml, kích thước của cameraView chưa đúng tỉ lệ 1:1. Ngoài ra, ta cũng sẽ bắt các sự kiện cho các button on/off flash và config 1 số thuộc tính của cameraview:

    private void setUpCameraView() {
        int screenWidth = DeviceUtils.getScreenWidth(this);
        int screenHeight = DeviceUtils.getScreenHeight(this);
        //set vùng camera thành vùng vuông
        rlCameraView.getLayoutParams().height = screenWidth;
        cameraView.getLayoutParams().height = screenWidth;
        cameraView.presetCameraForward(true);
        cameraView.presetRecordingSize(screenHeight, screenHeight);
        cameraView.setPictureSize(screenHeight, screenHeight, true); // > 4MP

        cameraView.setZOrderOnTop(false);
        cameraView.setZOrderMediaOverlay(true);
        btFlashMode.setOnClickListener(new View.OnClickListener() {
            int flashIndex = 0;
            String[] flashModes = {
                    Camera.Parameters.FLASH_MODE_TORCH,
                    Camera.Parameters.FLASH_MODE_AUTO,
            };

            @Override
            public void onClick(View v) {
                cameraView.setFlashLightMode(flashModes[flashIndex]);
                ++flashIndex;
                flashIndex %= flashModes.length;
                if (flashIndex == 0) {
                    btFlashMode.setImageResource(R.drawable.ic_turn_on_flash);
                } else {
                    btFlashMode.setImageResource(R.drawable.ic_turn_off_flash);
                }
            }
        });
        
    }

Ok, việc setup cho cameraview đến đây gần như đã xong. Tuy nhiên còn 1 bước nữa. Như đã giới thiệu ở bài trước, mình có sử dụng những file LUT chứa trong thư mục assets để tạo ra hiệu ứng cho camera. Vì vậy, ta cần khai báo (load) các file LUT này cho đối tượng cameraView của chúng ta. Trước hết, hãy tạo 1 callback theo dõi việc load các file LUT success hay failed:

    private CGENativeLibrary.LoadImageCallback mLoadImageCallback = new CGENativeLibrary.LoadImageCallback() {

        @Override
        public Bitmap loadImage(String name, Object arg) {

            Log.i(Common.LOG_TAG, "Loading file: " + name);
            AssetManager am = getAssets();
            InputStream is;
            try {
                is = am.open(name);
            } catch (IOException e) {
                Log.e(Common.LOG_TAG, "Can not open file " + name);
                return null;
            }

            return BitmapFactory.decodeStream(is);
        }

        @Override
        public void loadImageOK(Bitmap bmp, Object arg) {
            Log.i(Common.LOG_TAG, "Loading bitmap over, you can choose to recycle or cache");
            bmp.recycle();
        }
    };

Sau khi tạo callback xong, ta thêm câu lệnh sau vào phương thức setUpCameraView để load các file LUT:

CGENativeLibrary.setLoadImageCallback(mLoadImageCallback, null);

Ok, vậy là các bước config cho cameraView đã hoàn thành. Ta sẽ gọi hàm setUpCameraView vừa viết ở trong hàm createView:

    @Override
    protected void createView() {
        setUpCameraView();
    }

và không quên tắt camera khi rời khỏi activity hiện tại:

    @Override
    public void onResume() {
        super.onResume();
        cameraView.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();
        CameraInstance.getInstance().stopCamera();
        cameraView.release(null);
        cameraView.onPause();
    }

 

Xây dựng list filter

Bây giờ việc tiếp theo của ta là hiển thị list các filter để cho người dùng chọn trong lúc chụp ảnh. Ta sẽ dùng recyclerView để hiển thị các filter này. Việc đầu tiên chúng ta cần làm để xây dựng list filter là định nghĩa 1 đối tượng chứa các thông tin về filter đó.

Đối tượng FilterData.

Mình sẽ tạo 1 package model. Bên trong chứa lớp FilterData có nội dung như sau:

package fbphoto.thou.com.model;

import java.io.Serializable;

/**
 * Created by Computer on 2/8/2018.
 */

public class FilterData implements Serializable {

    String name;
    String rule;
    int imageId;

    public FilterData(String name, String rule, int imageId) {
        this.name = name;
        this.rule = rule;
        this.imageId = imageId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getRule() {
        return rule;
    }

    public void setRule(String rule) {
        this.rule = rule;
    }

    public int getImageId() {
        return imageId;
    }

    public void setImageId(int imageId) {
        this.imageId = imageId;
    }
}

Như các bạn thấy lớp này chỉ là 1 lớp Java thuần túy, không có gì đặc biệt cả. Nó chứa 3 trường:

  • name: Tên hiển thị trên app của filter.
  • rule: config của filter để thư viện xử lý ảnh thực hiện theo. Thực chất đây là tên các file LUT. Nếu các bạn tìm hiểu sâu thêm về thư viện xử lý ảnh này, các bạn sẽ thấy rằng có thể viết các config để thực hiện bất kỳ hiệu ứng nào ta mong muốn (chỉ cần có giá trị tham số). Tuy nhiên mình sẽ viết ở 1 bài khác. Ở bài này ta chỉ cần quan tâm đến tên config chứa file LUT mà thôi.
  • imageId: Id ảnh preview của filter. Mình sẽ đặt sẵn các ảnh preview ứng với từng filter trong thư mục drawable. Và trường imageId này sẽ có giá trị là id của các ảnh đó để hiện thị lên trên app.

Như vậy là ta đã xây dựng xong đối tượng chứa các thông tin về 1 filter. Giờ ta sẽ xây dựng adapter hiển thị list filter.

Xây dựng List Filter Adapter

Đầu tiên ta sẽ tạo layout cho các item của adapter, đặt tên là item_list_filter.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_filter"
    android:layout_width="@dimen/_80sdp"
    android:layout_height="wrap_content"
    android:layout_marginBottom="@dimen/_6sdp"
    android:layout_marginLeft="@dimen/_3sdp"
    android:layout_marginRight="@dimen/_3sdp"
    android:background="@drawable/bg_item_filter_unselected"
    android:gravity="center_horizontal"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/iv_filter_image"
        android:layout_width="@dimen/_65sdp"
        android:layout_height="@dimen/_65sdp"
        android:layout_marginTop="@dimen/_2sdp"
        android:scaleType="fitXY" />

    <TextView
        android:id="@+id/tv_filter_name"
        style="@style/Roboto_Regular"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/_3sdp"
        android:textColor="#3c3837"
        android:textSize="14sp" />

</LinearLayout>

Hãy để ý tới dòng:

android:background="@drawable/bg_item_filter_unselected"

Khi 1 filter được chọn, nó sẽ có 1 background riêng, để dễ phân biệt với các filter còn lại. Ta sẽ viết 2 file xml để thể hiện 2 trạng thái (được chọn và không được chọn) của filter. Trong thư mục drawable, tạo file bg_item_filter_selected.xml tương ứng với trạng thái filter đang được chọn:

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

    <stroke
        android:width="1dp"
        android:color="@color/colorPrimary"></stroke>

</shape>

Tạo thêm 1 file bg_item_filter_unselected.xml

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

</shape>

Như các bạn thấy, ở trạng thái không được chọn, ta sẽ set cho filter đó có 1 background trống trơn.

Ok, mọi thứ liên quan đến layout đã xong, giờ ta sẽ viết adapter cho list filter. Ta tạo package adapter và định nghĩa lớp ListFilterAdapter như sau:

package fbphoto.thou.com.adapter;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

import butterknife.Bind;
import butterknife.ButterKnife;
import fbphoto.thou.com.model.FilterData;
import fbphoto.thou.com.myapplication.R;

/**
 * Created by Computer on 2/8/2018.
 */

public class ListFilterAdapter extends RecyclerView.Adapter<ListFilterAdapter.FilterViewHolder> {

    List<FilterData> listFilter;
    int currentPosition = 0;

    public ListFilterAdapter(List<FilterData> listFilter) {
        this.listFilter = listFilter;
    }

    @Override
    public FilterViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list_filter, parent, false);
        return new FilterViewHolder(view);
    }

    @Override
    public void onBindViewHolder(FilterViewHolder holder, int position) {
        holder.setData(listFilter.get(position), position);
    }

    @Override
    public int getItemCount() {
        return listFilter.size();
    }

    class FilterViewHolder extends RecyclerView.ViewHolder {
        @Bind(R.id.ll_filter)
        View llFilter;
        @Bind(R.id.iv_filter_image)
        ImageView ivFilterImage;
        @Bind(R.id.tv_filter_name)
        TextView tvFilterName;

        public FilterViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }

        public void setData(final FilterData filterData, final int position) {
            ivFilterImage.setImageResource(filterData.getImageId());
            tvFilterName.setText(filterData.getName());
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (onFilterSelect != null) {
                        int oldFocusPosition = currentPosition;
                        currentPosition = position;
                        notifyItemChanged(oldFocusPosition);
                        notifyItemChanged(position);
                        onFilterSelect.onSelect(filterData);
                    }
                }
            });
            if (position == currentPosition) {
                llFilter.setBackgroundResource(R.drawable.bg_item_filter_selected);
            } else {
                llFilter.setBackgroundResource(R.drawable.bg_item_filter_unselected);
            }
        }
    }

    public interface OnFilterSelect {
        void onSelect(FilterData filterData);
    }

    OnFilterSelect onFilterSelect;

    public OnFilterSelect getOnFilterSelect() {
        return onFilterSelect;
    }

    public void setOnFilterSelect(OnFilterSelect onFilterSelect) {
        this.onFilterSelect = onFilterSelect;
    }

    public int getCurrentPosition() {
        return currentPosition;
    }

    public void setCurrentPosition(int currentPosition) {
        this.currentPosition = currentPosition;
    }
}

Adapter này cực kỳ đơn giản, nó chỉ chứa 1 interface xử lý sự kiện chọn filter, các bạn hãy tìm hiểu nhé. Ok, như vậy ta đã xây dựng xong adapter, giờ sẽ hiển thị nó lên app.

Hiển thị list filter

Quay trở lại MainActivity, mình sẽ tạo ra 1 mảng các String, mảng này chứa tên các file LUT mà mình sẽ truyền vào các đối tượng FilterData:

public static final String EFFECT_CONFIGS[] = {
            "@adjust lut original.png",
            "@adjust lut natural01.png",
            "@adjust lut natural02.png",
            "@adjust lut pure01.png",
            "@adjust lut pure02.png",
            "@adjust lut lovely01.png",
            "@adjust lut lovely02.png",
            "@adjust lut lovely03.png",
            "@adjust lut lovely04.png",
            "@adjust lut warm01.png",
            "@adjust lut warm02.png",
            "@adjust lut cool01.png",
            "@adjust lut cool02.png",
            "@adjust lut vintage.png",
            "@adjust lut gray.png",
    };

Và bây giờ sẽ tạo 1 phương thức setUpListFilterEffect để hiển thị list các filter lên recyclerview:

    FilterData seletedFilterData = new FilterData("None", EFFECT_CONFIGS[0], 0);
    private void setUpListFilterEffect() {
        //list danh sách hiệu ứng nằm ngang
        rvFilter.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
        //create list filter
        List<FilterData> listFilter = new ArrayList<>();
        int[] imageFilterId = {R.drawable.original_1, R.drawable.natural_1, R.drawable.natural_2
                , R.drawable.pure_1, R.drawable.pure_2, R.drawable.pinky_1
                , R.drawable.pinky_2, R.drawable.pinky_3, R.drawable.pinky_4
                , R.drawable.warm_1, R.drawable.warm_2, R.drawable.cool_1
                , R.drawable.cool_2, R.drawable.mood, R.drawable.bw};
        for (int i = 0; i < EFFECT_CONFIGS.length; i++) {
            //listFilter.add(new FilterData(EFFECT_CONFIGS[i], imageFilterId[i]));
            if (i == 0) {
                listFilter.add(new FilterData("Original", EFFECT_CONFIGS[i], imageFilterId[i]));
            } else if (i == 1) {
                listFilter.add(new FilterData("Natural 1", EFFECT_CONFIGS[i], imageFilterId[i]));
            } else if (i == 2) {
                listFilter.add(new FilterData("Natural 2", EFFECT_CONFIGS[i], imageFilterId[i]));
            } else if (i == 3) {
                listFilter.add(new FilterData("Pure 1", EFFECT_CONFIGS[i], imageFilterId[i]));
            } else if (i == 4) {
                listFilter.add(new FilterData("Pure 2", EFFECT_CONFIGS[i], imageFilterId[i]));
            } else if (i == 5) {
                listFilter.add(new FilterData("Pinky 1", EFFECT_CONFIGS[i], imageFilterId[i]));
            } else if (i == 6) {
                listFilter.add(new FilterData("Pinky 2", EFFECT_CONFIGS[i], imageFilterId[i]));
            } else if (i == 7) {
                listFilter.add(new FilterData("Pinky 3", EFFECT_CONFIGS[i], imageFilterId[i]));
            } else if (i == 8) {
                listFilter.add(new FilterData("Pinky 4", EFFECT_CONFIGS[i], imageFilterId[i]));
            } else if (i == 9) {
                listFilter.add(new FilterData("Warm 1", EFFECT_CONFIGS[i], imageFilterId[i]));
            } else if (i == 10) {
                listFilter.add(new FilterData("Warm 2", EFFECT_CONFIGS[i], imageFilterId[i]));
            } else if (i == 11) {
                listFilter.add(new FilterData("Cool 1", EFFECT_CONFIGS[i], imageFilterId[i]));
            } else if (i == 12) {
                listFilter.add(new FilterData("Cool 2", EFFECT_CONFIGS[i], imageFilterId[i]));
            } else if (i == 13) {
                listFilter.add(new FilterData("Mood", EFFECT_CONFIGS[i], imageFilterId[i]));
            } else if (i == 14) {
                listFilter.add(new FilterData("B&W", EFFECT_CONFIGS[i], imageFilterId[i]));
            }
        }
        ListFilterAdapter filterAdapter = new ListFilterAdapter(listFilter);
        filterAdapter.setOnFilterSelect(new ListFilterAdapter.OnFilterSelect() {
            @Override
            public void onSelect(FilterData filterData) {
                seletedFilterData = filterData;
                cameraView.setFilterWithConfig(filterData.getRule());
            }
        });
        rvFilter.setAdapter(filterAdapter);
    }

Như các bạn thấy, phương thức trên khởi tạo ra 1 list các đối tượng FilterData và truyền list đó vào adapter để hiển thị lên recyclerView. Đồng thời ta cũng implement các câu lệnh khi có 1 filter được chọn:

        filterAdapter.setOnFilterSelect(new ListFilterAdapter.OnFilterSelect() {
            @Override
            public void onSelect(FilterData filterData) {
                seletedFilterData = filterData;
                cameraView.setFilterWithConfig(filterData.getRule());
            }
        });

Ok, hy vọng code mình viết đủ rõ ràng để các bạn có thể hiểu. Giờ ta sẽ gọi phương thức setUpListFilterEffect này trong hàm createView:

    @Override
    protected void createView() {
        setUpCameraView();
        setUpListFilterEffect();
    }

Chức năng chụp ảnh

Ta sử dụng ButterKnife để bắt sự kiện click vào button chụp ảnh:

    @OnClick({R.id.bt_take_picture})
    public void onTakePictureClick() {
        showToast("Đang chụp ảnh...");
        cameraView.takeShot(new CameraRecordGLSurfaceView.TakePictureCallback() {
            @Override
            public void takePictureOK(Bitmap bmp) {
                File file = new File(Environment.getExternalStorageDirectory() + "/FilterImageDemo");
                if (!file.exists()) {
                    file.mkdirs();
                }
                if (bmp != null) {
                    String imagePath = ImageUtil.saveBitmap(bmp, file.getAbsolutePath() + "/" + System.currentTimeMillis() + ".jpg");
                    bmp.recycle();
                    //showToast("Đã xong!");
                    Bundle bundle = new Bundle();
                    bundle.putString(Constant.KEY_IMAGE_PATH, imagePath);
                    bundle.putSerializable(Constant.KEY_FILTER, seletedFilterData);
                    showActivity(CameraResultActivity.class, bundle);
                } else {
                    showToast("Ôi, có lỗi rồi!");
                }
            }
        });
    }

Như bạn thấy, khi click vào nút chụp ảnh, ta sẽ lưu lại 1 file jpg trong thư mục FilterImageDemo và nhảy sang màn hình chỉnh sửa ảnh (CameraResultActivity). Ta cũng truyền dữ liệu về filter đang được chọn và đường dẫn ảnh đã lưu sang màn hình chỉnh sửa. Tuy nhiên giờ ta chưa cần quan tâm đến màn hình chỉnh sửa ảnh này. Đơn giản hãy tạo 1 activity có tên CameraResultActivity và khai báo nó trong file AndroidManifest:

        <activity
            android:name=".CameraResultActivity"
            android:screenOrientation="portrait"></activity>

Chức năng tùy chọn camera trước (sau)

Ta sử dụng ButterKnife để bắt sự kiện click cho button đảo camera:

    @OnClick(R.id.bt_rotate_camera)
    public void rotateCamera() {
        cameraView.switchCamera();
    }

Chức năng đóng màn hình camera

Ta sử dụng ButterKnife để bắt sự kiện click cho button thoát khỏi màn hình camera:

    @OnClick(R.id.bt_close)
    public void closeCamera() {
        finish();
    }

Chức năng chọn ảnh trong gallery

Ta viết thêm 1 chức năng cho phép người dùng chọn ảnh trong gallery để chỉnh sửa thay vì chụp ảnh trực tiếp.

    @OnClick(R.id.iv_pick_image)
    public void pickImage() {
        Bundle bundle = new Bundle();
        showActivity(CameraResultActivity.class, bundle);
    }

Như các bạn thấy khi ấn vào button pick image, ta đơn giản hiển thị màn hình CameraResultActivity. Khác với khi chụp ảnh, ta không truyền thêm dữ liệu nào khi chuyển qua màn hình CameraResultActivity này. Như vậy ở trong lớp CameraResultActivity, ta sẽ cần xử lý 2 trường hợp:

  • Không có data truyền sang (chọn ảnh trong gallery)
  • Có data truyền sang, hiển thị ảnh với các dữ liệu truyền từ màn hình chụp ảnh.

Tổng kết

Như vậy ở bài này mình đã hướng dẫn xong tính năng chụp ảnh. Ở bài tiếp theo (và cũng là bài cuối cùng) mình sẽ hướng dẫn nốt về chức năng chỉnh sửa cũng như chia sẻ ảnh được chọn. Hãy theo dõi nhé.

Source code.

 

 

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *