Hướng dẫn tạo bộ lọc màu ảnh giống như trong Instagram phần 2
Bài viết lần này sẽ tiếp tục hướng dẫn các bạn viết ứng dụng lọc ảnh, các bạn có thể xem lại phần 1 bài viết này tại đây Ở phàn 1 chúng ta đã có tất cả các class bắt buộc. Bây giờ hãy cùng tạo class adapter của RecyclerView trước khi chuyển sang giao diện người dùng thực tế. Bước 10 Tạo một ...
Bài viết lần này sẽ tiếp tục hướng dẫn các bạn viết ứng dụng lọc ảnh, các bạn có thể xem lại phần 1 bài viết này tại đây
Ở phàn 1 chúng ta đã có tất cả các class bắt buộc. Bây giờ hãy cùng tạo class adapter của RecyclerView trước khi chuyển sang giao diện người dùng thực tế.
Bước 10
Tạo một file layout mới có tên thumbnail_list_item.xml. Layout này chứa một TextView và ImageView để hiển thị tên bộ lọc và hình thumbnail.
thumbnail_list_item.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_awidth="wrap_content" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/filter_name" android:layout_awidth="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="5dp" android:layout_marginTop="5dp" android:fontFamily="@string/roboto_medium" /> <ImageView android:id="@+id/thumbnail" android:layout_awidth="@dimen/thumbnail_size" android:layout_height="@dimen/thumbnail_size" android:src="@mipmap/ic_launcher" /> </LinearLayout>
Bước 11
Tạo một class tên là ThumbnailsAdapter.java, class này hoạt động như bộ thu nhỏ của RecyclerView để hiển thị hình ảnh được lọc trong danh sách ngang.
ThumbnailsAdapter.java package info.androidhive.imagefilters; import android.content.Context; import android.support.v4.content.ContextCompat; 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 com.zomato.photofilters.imageprocessors.Filter; import com.zomato.photofilters.utils.ThumbnailItem; import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; /** * Created by ravi on 23/10/17. */ public class ThumbnailsAdapter extends RecyclerView.Adapter<ThumbnailsAdapter.MyViewHolder> { private List<ThumbnailItem> thumbnailItemList; private ThumbnailsAdapterListener listener; private Context mContext; private int selectedIndex = 0; public class MyViewHolder extends RecyclerView.ViewHolder { @BindView(R.id.thumbnail) ImageView thumbnail; @BindView(R.id.filter_name) TextView filterName; public MyViewHolder(View view) { super(view); ButterKnife.bind(this, view); } } public ThumbnailsAdapter(Context context, List<ThumbnailItem> thumbnailItemList, ThumbnailsAdapterListener listener) { mContext = context; this.thumbnailItemList = thumbnailItemList; this.listener = listener; } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.thumbnail_list_item, parent, false); return new MyViewHolder(itemView); } @Override public void onBindViewHolder(MyViewHolder holder, final int position) { final ThumbnailItem thumbnailItem = thumbnailItemList.get(position); holder.thumbnail.setImageBitmap(thumbnailItem.image); holder.thumbnail.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { listener.onFilterSelected(thumbnailItem.filter); selectedIndex = position; notifyDataSetChanged(); } }); holder.filterName.setText(thumbnailItem.filterName); if (selectedIndex == position) { holder.filterName.setTextColor(ContextCompat.getColor(mContext, R.color.filter_label_selected)); } else { holder.filterName.setTextColor(ContextCompat.getColor(mContext, R.color.filter_label_normal)); } } @Override public int getItemCount() { return thumbnailItemList.size(); } public interface ThumbnailsAdapterListener { void onFilterSelected(Filter filter); } }
Bây giờ chúng ta sẽ tạo một class Fragment để hiển thị hìnhthumbnails của bức ảnh được lọc trong danh sách nằm ngang. Để đạt được điều này, chúng ta cần một RecyclerView và cung cấp danh sách các hình ảnh thu nhỏ cho class adapter.
Bước 12
Tạo một Fragment mới bằng cách vào File ⇒ New ⇒ Fragment ⇒ Fragment (Blank) và đặt tên nó là FiltersListFragment.java.
Bước 13
Mở file layout của fragment_filters_list.xml fragment và thêm phần tử RecyclerView.
fragment_filters_list.xml <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_awidth="match_parent" android:layout_height="match_parent" tools:context="info.androidhive.imagefilters.FiltersListFragment"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_gravity="center_vertical" android:layout_awidth="match_parent" android:layout_height="wrap_content" android:clipChildren="false" android:padding="4dp" android:scrollbars="none" /> </FrameLayout>
Bước 14
Mở file FiltersListFragment.java và thực hiện các sửa đổi như được hiển thị bên dưới.
FilterPack.getFilterPack () cung cấp danh sách các bộ lọc có sẵn từ thư viện.
Trong phương thức prepareThumbnail () lọc qua và mỗi thumbnail item được thêm vào ThumbnailsManager để xử lý chúng. Các hình thumbnail được xử lý được thêm vào lại vào thumbnailItemList là tài nguyên dữ liệu cho RecyclerView.
Khi bộ dữ liệu hình thu nhỏ đã sẵn sàng, mAdapter.notifyDataSetChanged () được gọi để hiển thị danh sách. Tất cả điều này đã được thực hiện trong background thread như quá trình xử lý hình ảnh
Interfac FiltersListFragmentListener cung cấp phương thức gọi lại chomain activity bất cứ khi nào một bộ lọc mới được chọn.
Việc xử lý thực tế bộ lọc hình ảnh đã chọn sẽ được nói cụ thể khi nói về MainActivity.
FiltersListFragment.java package info.androidhive.imagefilters; import android.graphics.Bitmap; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.zomato.photofilters.FilterPack; import com.zomato.photofilters.imageprocessors.Filter; import com.zomato.photofilters.utils.ThumbnailItem; import com.zomato.photofilters.utils.ThumbnailsManager; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import butterknife.BindView; import butterknife.ButterKnife; import info.androidhive.imagefilters.utils.BitmapUtils; import info.androidhive.imagefilters.utils.SpacesItemDecoration; public class FiltersListFragment extends Fragment implements ThumbnailsAdapter.ThumbnailsAdapterListener { @BindView(R.id.recycler_view) RecyclerView recyclerView; ThumbnailsAdapter mAdapter; List<ThumbnailItem> thumbnailItemList; FiltersListFragmentListener listener; public void setListener(FiltersListFragmentListener listener) { this.listener = listener; } public FiltersListFragment() { // Required empty public constructor } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_filters_list, container, false); ButterKnife.bind(this, view); thumbnailItemList = new ArrayList<>(); mAdapter = new ThumbnailsAdapter(getActivity(), thumbnailItemList, this); RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false); recyclerView.setLayoutManager(mLayoutManager); recyclerView.setItemAnimator(new DefaultItemAnimator()); int space = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics()); recyclerView.addItemDecoration(new SpacesItemDecoration(space)); recyclerView.setAdapter(mAdapter); prepareThumbnail(null); return view; } /** * Renders thumbnails in horizontal list * loads default image from Assets if passed param is null * * @param bitmap */ public void prepareThumbnail(final Bitmap bitmap) { Runnable r = new Runnable() { public void run() { Bitmap thumbImage; if (bitmap == null) { thumbImage = BitmapUtils.getBitmapFromAssets(getActivity(), MainActivity.IMAGE_NAME, 100, 100); } else { thumbImage = Bitmap.createScaledBitmap(bitmap, 100, 100, false); } if (thumbImage == null) return; ThumbnailsManager.clearThumbs(); thumbnailItemList.clear(); // add normal bitmap first ThumbnailItem thumbnailItem = new ThumbnailItem(); thumbnailItem.image = thumbImage; thumbnailItem.filterName = getString(R.string.filter_normal); ThumbnailsManager.addThumb(thumbnailItem); List<Filter> filters = FilterPack.getFilterPack(getActivity()); for (Filter filter : filters) { ThumbnailItem tI = new ThumbnailItem(); tI.image = thumbImage; tI.filter = filter; tI.filterName = filter.getName(); ThumbnailsManager.addThumb(tI); } thumbnailItemList.addAll(ThumbnailsManager.processThumbs(getActivity())); getActivity().runOnUiThread(new Runnable() { @Override public void run() { mAdapter.notifyDataSetChanged(); } }); } }; new Thread(r).start(); } @Override public void onFilterSelected(Filter filter) { if (listener != null) listener.onFilterSelected(filter); } public interface FiltersListFragmentListener { void onFilterSelected(Filter filter); } }
Bây giờ chúng ta sẽ thêm phần controls ảnh để điều khiển độ sáng, độ tương phản và độ bão hòa.
Bước 15
Tạo thêm một fragment có tên EditImageFragment.java bằng cách thực hiện theo các bước. Mở file layout của fragment có tên là fragment_edit_image.xml này và thực hiện các sửa đổi như dưới đây.
Trong layoyt này chúng ta sẽ thêm ba view: SeekBar để điều khiển độ sáng, độ tương phản và độ bão hòa của hình ảnh.
fragment_edit_image.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_awidth="match_parent" android:layout_height="match_parent" android:gravity="center_vertical" android:orientation="vertical" android:paddingLeft="@dimen/margin_horizontal" android:paddingRight="@dimen/margin_horizontal" tools:context="info.androidhive.imagefilters.EditImageFragment"> <LinearLayout android:layout_awidth="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingBottom="@dimen/padding_10" android:paddingTop="@dimen/padding_10"> <TextView android:layout_awidth="@dimen/lbl_edit_image_control" android:layout_height="wrap_content" android:text="@string/lbl_brightness" /> <SeekBar android:id="@+id/seekbar_brightness" android:layout_awidth="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> </LinearLayout> <LinearLayout android:layout_awidth="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingBottom="@dimen/padding_10" android:paddingTop="@dimen/padding_10"> <TextView android:layout_awidth="@dimen/lbl_edit_image_control" android:layout_height="wrap_content" android:text="@string/lbl_contrast" /> <SeekBar android:id="@+id/seekbar_contrast" android:layout_awidth="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> </LinearLayout> <LinearLayout android:layout_awidth="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingBottom="@dimen/padding_10" android:paddingTop="@dimen/padding_10"> <TextView android:layout_awidth="@dimen/lbl_edit_image_control" android:layout_height="wrap_content" android:text="@string/lbl_saturation" /> <SeekBar android:id="@+id/seekbar_saturation" android:layout_awidth="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> </LinearLayout> </LinearLayout>
Bước 16
Mở EditImageFragment.java và thực hiện các sửa đổi như dưới đây.
Trong phương thức onCreateView, các widgets Seekbar được khởi tạo với giá trị ban đầu và giá trị cực đại. Đối với độ sáng, giá trị có thể từ -100 / 100. Độ tương phản và độ bão hòa có giá trị nổi.
Interface EditImageFragmentListener cung cấp các phương thức gọi lại bất cứ khi nào khi các giá trị Seekbar thay đổi.
Việc xử lý độ sáng, độ tương phản và độ bão hòa một lần nữa được nói cụ thể trong MainActivity khi gọi lại.
EditImageFragment.java package info.androidhive.imagefilters; import android.os.Bundle; import android.support.v4.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.SeekBar; import butterknife.BindView; import butterknife.ButterKnife; public class EditImageFragment extends Fragment implements SeekBar.OnSeekBarChangeListener { private EditImageFragmentListener listener; @BindView(R.id.seekbar_brightness) SeekBar seekBarBrightness; @BindView(R.id.seekbar_contrast) SeekBar seekBarContrast; @BindView(R.id.seekbar_saturation) SeekBar seekBarSaturation; public void setListener(EditImageFragmentListener listener) { this.listener = listener; } public EditImageFragment() { // Required empty public constructor } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_edit_image, container, false); ButterKnife.bind(this, view); // keeping brightness value b/w -100 / +100 seekBarBrightness.setMax(200); seekBarBrightness.setProgress(100); // keeping contrast value b/w 1.0 - 3.0 seekBarContrast.setMax(20); seekBarContrast.setProgress(0); // keeping saturation value b/w 0.0 - 3.0 seekBarSaturation.setMax(30); seekBarSaturation.setProgress(10); seekBarBrightness.setOnSeekBarChangeListener(this); seekBarContrast.setOnSeekBarChangeListener(this); seekBarSaturation.setOnSeekBarChangeListener(this); return view; } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean b) { if (listener != null) { if (seekBar.getId() == R.id.seekbar_brightness) { // brightness values are b/w -100 to +100 listener.onBrightnessChanged(progress - 100); } if (seekBar.getId() == R.id.seekbar_contrast) { // converting int value to float // contrast values are b/w 1.0f - 3.0f // progress = progress > 10 ? progress : 10; progress += 10; float floatVal = .10f * progress; listener.onContrastChanged(floatVal); } if (seekBar.getId() == R.id.seekbar_saturation) { // converting int value to float // saturation values are b/w 0.0f - 3.0f float floatVal = .10f * progress; listener.onSaturationChanged(floatVal); } } } @Override public void onStartTrackingTouch(SeekBar seekBar) { if (listener != null) listener.onEditStarted(); } @Override public void onStopTrackingTouch(SeekBar seekBar) { if (listener != null) listener.onEditCompleted(); } public void resetControls() { seekBarBrightness.setProgress(100); seekBarContrast.setProgress(0); seekBarSaturation.setProgress(10); } public interface EditImageFragmentListener { void onBrightnessChanged(int brightness); void onSaturationChanged(float saturation); void onContrastChanged(float contrast); void onEditStarted(); void onEditCompleted(); } }
Bây giờ chúng ta đã có các fragments , chúng ta hãy cùng xem cách kết hợp chúng để đạt được kết quả cuối cùng
Bước 17
Mở file layout activity_main.xml và content_main.xml và NonSwipeableViewPager và TabLayout và thêm đoạn code sau
activity_main.xml <?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout 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_awidth="match_parent" android:layout_height="match_parent" tools:context="info.androidhive.imagefilters.MainActivity"> <android.support.design.widget.AppBarLayout android:layout_awidth="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_awidth="match_parent" android:layout_height="?attr/actionBarSize" android:background="@android:color/white" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <include layout="@layout/content_main" /> </android.support.design.widget.CoordinatorLayout>
content_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_awidth="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:orientation="vertical" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context="info.androidhive.imagefilters.MainActivity" tools:showIn="@layout/activity_main"> <ImageView android:id="@+id/image_preview" android:layout_awidth="match_parent" android:layout_height="360dp" android:scaleType="centerCrop" /> <info.androidhive.imagefilters.utils.NonSwipeableViewPager android:id="@+id/viewpager" android:layout_awidth="match_parent" android:layout_height="120dp" android:layout_above="@+id/tabs" android:layout_below="@+id/image_preview" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> <android.support.design.widget.TabLayout android:id="@+id/tabs" android:layout_awidth="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" app:tabGravity="fill" app:tabMode="fixed" /> </RelativeLayout>
Bước 18
Mở file MainActivity.java và thực hiện các thay đổi như dưới đây.
System.loadLibrary ("NativeImageProcessor") được gọi để khởi tạo thư viện gốc.
FiltersListFragment và EditImageFragments được thêm vào ViewPager trong phương thức setupViewPager ().
onFilterSelected () sẽ được gọi khi bộ lọc được chọn trong FiltersListFragment. Bộ lọc đã chọn được xử lý và hình ảnh cuối cùng được hiển thị imagePreview.
onBrightnessChanged (), onSaturationChanged () và onContrastChanged () sẽ được gọi khi các giá trị Seekbar thay đổi trong EditImageFragments.
Hình ảnh cuối cùng sẽ được lưu vào Thư viện khi lựa chọn SAVE từ Thanh công cụ.
MainActivity.java package info.androidhive.imagefilters; import android.Manifest; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.Snackbar; import android.support.design.widget.TabLayout; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ImageView; import android.widget.Toast; import com.karumi.dexter.Dexter; import com.karumi.dexter.MultiplePermissionsReport; import com.karumi.dexter.PermissionToken; import com.karumi.dexter.listener.PermissionRequest; import com.karumi.dexter.listener.multi.MultiplePermissionsListener; import com.zomato.photofilters.imageprocessors.Filter; import com.zomato.photofilters.imageprocessors.subfilters.BrightnessSubFilter; import com.zomato.photofilters.imageprocessors.subfilters.ContrastSubFilter; import com.zomato.photofilters.imageprocessors.subfilters.SaturationSubfilter; import java.util.ArrayList; import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; import info.androidhive.imagefilters.utils.BitmapUtils; public class MainActivity extends AppCompatActivity implements FiltersListFragment.FiltersListFragmentListener, EditImageFragment.EditImageFragmentListener { private static final String TAG = MainActivity.class.getSimpleName(); public static final String IMAGE_NAME = "dog.jpg"; public static final int SELECT_GALLERY_IMAGE = 101; @BindView(R.id.image_preview) ImageView imagePreview; @BindView(R.id.tabs) TabLayout tabLayout; @BindView(R.id.viewpager) ViewPager viewPager; @BindView(R.id.coordinator_layout) CoordinatorLayout coordinatorLayout; Bitmap originalImage; // to backup image with filter applied Bitmap filteredImage; // the final image after applying // brightness, saturation, contrast Bitmap finalImage; FiltersListFragment filtersListFragment; EditImageFragment editImageFragment; // modified image values int brightnessFinal = 0; float saturationFinal = 1.0f; float contrastFinal = 1.0f; // load native image filters library static { System.loadLibrary("NativeImageProcessor"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setTitle(getString(R.string.activity_title_main)); loadImage(); setupViewPager(viewPager); tabLayout.setupWithViewPager(viewPager); } private void setupViewPager(ViewPager viewPager) { ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager()); // adding filter list fragment filtersListFragment = new FiltersListFragment(); filtersListFragment.setListener(this); // adding edit image fragment editImageFragment = new EditImageFragment(); editImageFragment.setListener(this); adapter.addFragment(filtersListFragment, getString(R.string.tab_filters)); adapter.addFragment(editImageFragment, getString(R.string.tab_edit)); viewPager.setAdapter(adapter); } @Override public void onFilterSelected(Filter filter) { // reset image controls resetControls(); // applying the selected filter filteredImage = originalImage.copy(Bitmap.Config.ARGB_8888, true); // preview filtered image imagePreview.setImageBitmap(filter.processFilter(filteredImage)); finalImage = filteredImage.copy(Bitmap.Config.ARGB_8888, true); } @Override public void onBrightnessChanged(final int brightness) { brightnessFinal = brightness; Filter myFilter = new Filter(); myFilter.addSubFilter(new BrightnessSubFilter(brightness)); imagePreview.setImageBitmap(myFilter.processFilter(finalImage.copy(Bitmap.Config.ARGB_8888, true))); } @Override public void onSaturationChanged(final float saturation) { saturationFinal = saturation; Filter myFilter = new Filter(); myFilter.addSubFilter(new SaturationSubfilter(saturation)); imagePreview.setImageBitmap(myFilter.processFilter(finalImage.copy(Bitmap.Config.ARGB_8888, true))); } @Override public void onContrastChanged(final float contrast) { contrastFinal = contrast; Filter myFilter = new Filter(); myFilter.addSubFilter(new ContrastSubFilter(contrast)); imagePreview.setImageBitmap(myFilter.processFilter(finalImage.copy(Bitmap.Config.ARGB_8888, true))); } @Override public void onEditStarted() { } @Override public void onEditCompleted() { // once the editing is done i.e seekbar is drag is completed, // apply the values on to filtered image final Bitmap bitmap = filteredImage.copy(Bitmap.Config.ARGB_8888, true); Filter myFilter = new Filter(); myFilter.addSubFilter(new BrightnessSubFilter(brightnessFinal)); myFilter.addSubFilter(new ContrastSubFilter(contrastFinal)); myFilter.addSubFilter(new SaturationSubfilter(saturationFinal)); finalImage = myFilter.processFilter(bitmap); } /** * Resets image edit controls to normal when new filter * is selected */ private void resetControls() { if (editImageFragment != null) { editImageFragment.resetControls(); } brightnessFinal = 0; saturationFinal = 1.0f; contrastFinal = 1.0f; } class ViewPagerAdapter extends FragmentPagerAdapter { private final List<Fragment> mFragmentList = new ArrayList<>(); private final List<String> mFragmentTitleList = new ArrayList<>(); public ViewPagerAdapter(FragmentManager manager) { super(manager); } @Override public Fragment getItem(int position) { return mFragmentList.get(position); } @Override public int getCount() { return mFragmentList.size(); } public void addFragment(Fragment fragment, String title) { mFragmentList.add(fragment); mFragmentTitleList.add(title); } @Override public CharSequence getPageTitle(int position) { return mFragmentTitleList.get(position); } } // load the default image from assets on app launch private void loadImage() { originalImage = BitmapUtils.getBitmapFromAssets(this, IMAGE_NAME, 300, 300); filteredImage = originalImage.copy(Bitmap.Config.ARGB_8888, true); finalImage = originalImage.copy(Bitmap.Config.ARGB_8888, true); imagePreview.setImageBitmap(originalImage); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_open) { openImageFromGallery(); return true; } if (id == R.id.action_save) { saveImageToGallery(); return true; } return super.onOptionsItemSelected(item); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK && requestCode == SELECT_GALLERY_IMAGE) { Bitmap bitmap = BitmapUtils.getBitmapFromGallery(this, data.getData(), 800, 800); // clear bitmap memory originalImage.recycle(); finalImage.recycle(); finalImage.recycle(); originalImage = bitmap.copy(Bitmap.Config.ARGB_8888, true); filteredImage = originalImage.copy(Bitmap.Config.ARGB_8888, true); finalImage = originalImage.copy(Bitmap.Config.ARGB_8888, true); imagePreview.setImageBitmap(originalImage); bitmap.recycle(); // render selected image thumbnails filtersListFragment.prepareThumbnail(originalImage); } } private void openImageFromGallery() { Dexter.withActivity(this).withPermissions(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE) .withListener(new MultiplePermissionsListener() { @Override public void onPermissionsChecked(MultiplePermissionsReport report) { if (report.areAllPermissionsGranted()) { Intent intent = new Intent(Intent.ACTION_PICK); intent.setType("image/*"); startActivityForResult(intent, SELECT_GALLERY_IMAGE); } else { Toast.makeText(getApplicationContext(), "Permissions are not granted!", Toast.LENGTH_SHORT).show(); } } @Override public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) { token.continuePermissionRequest(); } }).check(); } /* * saves image to camera gallery * */ private void saveImageToGallery() { Dexter.withActivity(this).withPermissions(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE) .withListener(new MultiplePermissionsListener() { @Override public void onPermissionsChecked(MultiplePermissionsReport report) { if (report.areAllPermissionsGranted()) { final String path = BitmapUtils.insertImage(getContentResolver(), finalImage, System.currentTimeMillis() + "_profile.jpg", null); if (!TextUtils.isEmpty(path)) { Snackbar snackbar = Snackbar .make(coordinatorLayout, "Image saved to gallery!", Snackbar.LENGTH_LONG) .setAction("OPEN", new View.OnClickListener() { @Override public void onClick(View view) { openImage(path); } }); snackbar.show(); } else { Snackbar snackbar = Snackbar .make(coordinatorLayout, "Unable to save image!", Snackbar.LENGTH_LONG); snackbar.show(); } } else { Toast.makeText(getApplicationContext(), "Permissions are not granted!", Toast.LENGTH_SHORT).show(); } } @Override public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) { token.continuePermissionRequest(); } }).check(); } // opening image in default image viewer app private void openImage(String path) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse(path), "image/*"); startActivity(intent); } }
Chạy ứng dụng và thử nghiệm một lần. Bạn sẽ thấy giao diện đẹp như trong bài báo. Bạn có thể áp dụng các bộ lọc khác nhau từ danh sách và có thể kiểm soát độ sáng, độ bão hòa và độ tương phản.
Source
Source code