You are on page 1of 19

Báo cáo đồ án tin học:

Xây dựng phần mềm

Đề tài: Xây dựng ứng dụng

chỉnh sửa ảnh

Thực hiện bởi:

Trần Minh Hải - 20161327 (trưởng nhóm)

Bế Trần Văn - 20164603

1
Mục lục
Lời mở đầu…………………………………………………………3
I. Giới thiệu về nhóm……………………………………………3-5
1. Giới thiệu chung……………………………………………...4
2. Mục tiêu, khó khăn và cách tiếp cận…………………………4
3. Cách thức hoạt động………………………………………….4-5
a) Cách lam việc của cả nhóm………………………………….4
b) Cách làm việc của mỗi thành viên…………………………..4-5
c) Phân chia công việc cụ thể…………………………………..5
d) Báo cáo tiến độ công việc theo tuần………………………....5
4. Những gì học được sau dự án………………………………...5
5. Kết quả làm việc và đánh giá…………………………………5
II. Hướng dẫn sử dụng……………………………………………6-16
1. Đối với người sử dụng………………………………………..6-8
2. Đối với người phát triển………………………………………8-16
a) Thiết kế hệ thống……………………………………………..9
b) Thiết kế giao diện……………………………………………9-11
i. Phác thảo logo bằng công cụ vẽ trên máy tính…………9-10
ii. Thiết kế giao diện trong android studio………………..10-11
iii. Sử dụng resources……………………………………...11
c) Lập trình cho hệ thống………………………………………..12-15
i. Các class có trong hệ thống………………………………..12-14
ii. Các bẫy lỗi đã sử dụng…………………………………...15
d) Đánh giá…………………………………………………….15
e) Định hướng phát triển………………………………………..15-16
III. Thảo luận………………………………………………………16-17
IV. Kết luận………………………………………………………...17

2
Lời mở đầu
Chụp ảnh là nhu cầu cần thiết trong cuộc sống hiện nay. Để có một
bức ảnh đẹp, ghi lại những khoảnh khắc đáng nhớ, không thể thiếu ứng
dụng chỉnh sửa ảnh.
Chính vì điều này, nhóm V_CH group đã quyết định làm một ứng
dụng chỉnh sửa ảnh có tính cạnh tranh. Ứng dụng này cần đảm bảo yếu
tố: đủ một số công cụ cơ bản như chỉnh sửa màu sắc, tốc độ nhanh và
dung lượng thấp. Từ đó, ứng dụng có thể được sự tin tưởng của người
dùng.
Sau hơn 3 tháng triển khai, nhóm V_CH group đã 1 phần hoàn thành
được những chỉ tiêu trên.
Chúng em xin phép được cảm ơn thầy Vũ Hải, người hướng dẫn
chính trong khi chúng em thực hiện dự án này.
Trưởng nhóm V_CH group,
Trần Minh Hải

I. Giới thiệu về nhóm


1.Giới thiệu chung

3
Nhóm có tên VCH_Group, với VCH lấy theo ba chữ cái đầu tronng
tên của ba thành viên ban đầu của nhóm gồm Văn , Hải và Cường. Nhóm
được thành lập nhằm phát triển một ứng dụng chỉnh sửa ảnh có đầy đủ
các công cụ cơ bản hoạt động trên nền tảng android 4.0 trở nên. Hiện tại
vì nhiều nguyên nhân khách quan VCH_Group có hai thành viên hoạt
động thường xuyên gồm:
- Trần Minh Hải (trưởng nhóm)
- Bế Trần Văn
Ngoài ra, không thể không kể đến sự hỗ trợ đắc lực, cùng với những
lời nhận xét thẳng thắn và khách quan đến từ thầy Vũ Hải cùng với tập
thể các bạn trong lớp CLC_THCN K61.
2.Mục tiêu, khó khăn và cách tiếp cận
 Mục tiêu khi bắt đầu dự án:
 Ra được ứng dụng chỉnh sửa ảnh trên điện thoại với
các công cụ cơ bản.
 Các thành viên cùng thực hiện dự án, hiểu về dự án
và có khả năng thuyết trình về công việc của mình
khi dự án kết thúc.
 Ứng dụng có tốc độ nhanh và dung lượng nhẹ.
 Khó khăn:
 Chuyên môn của từng thành viên chưa đồng đều,
chưa sử dụng phần mềm android studio thành thục.
 Kỹ năng làm việc nhóm còn kém.
 Chưa có kinh nghiệm thiết kế app trên nền tảng di
động.
 Thời gian làm việc hạn chế.
 Cách tiếp cận:
 Mô hình thác nước.
 Chuyên môn hóa từng thành viên.
3.Cách thức hoạt động
a. Cách làm việc của cả nhóm
 Phân chia công việc ngay sau khi báo cáo vào buổi
chiều thứ 6 hàng tuần.
 Họp online qua facebook cùng nhau giải quyết những
khó khăn.
b. Cách àm việc của mỗi thành viên
 Nhận công việc phù hợp với khả năng cá nhân.
 Có trách nhiệm với công việc của bản thân, giúp đỡ các
thành viên khác nếu có thể.
 Mỗi thành viên là một người giám sát với các thành
viên khác trong nhóm.
c. Phân chia công việc cụ thể

4
 Trần Minh Hải: Hoàn thành các công cụ cơ bản cho app
chỉnh sửa ảnh, là thư kí của nhóm.
 Bế Trần Văn: Thiết kế và tạo thành giao diện cho app.
Tạo và quản lí blog để phân chia công việc cho mỗi cá
nhân theo từng tuần.
d.Báo cáo tiến độ theo tuần.

4.Những gì học được sau dự án


 Làm việc:
- Cách làm việc nhóm: Phân chia công việc.
- Vai trò của làm việc có chiến lược(thác nước).
- Tạo và quản lí blogger.
 Thuyết trình và viết báo cáo đồ án.
 Kĩ thuật:
- Làm việc trên android studio: biết cách thiết kế user
interface và lập trình java cho một ứng dụng.
- Xây dựng một ứng dụng dễ đọc cho người phát triển
sau(resources).
5.Kết quả làm được và đánh giá.
Nhóm đã phát triển thành công ứng dụng chỉnh sửa ảnh trên nền tảng
android phù hợp với tiêu chí ứng dụng nhanh và gọn. Mặc dù ứng dụng
chỉ mới có công cụ filter color ảnh do hạn chế về mặt thời gian cũng như
có sự biến động trong số lượng thành viên nhóm, nhưng đây cũng là
thành công bước đầu và là nền tảng vững chắc để nhóm tiếp tục phát triển
ứng dụng này , cũng như các ứng dụng khác trên nền tảng android trong
tương lai.
Định hướng phát triển ứng dụng:
- Thêm công cụ crop, add text, emoji…
- Hoàn thiện chức năng load ảnh.
- Hoàn thiện chức năng cài đặt.
II. Hướng dẫn sử dụng
1. Đối với người sử dụng
Để sử dụng ứng dụng chỉnh sửa ảnh này, người sử dụng cần cung
cấp cho chúng tôi quyền truy cập vào bộ nhớ của thiết bị.

5
Ứng dụng chỉnh sửa ảnh gồm 3 hoạt động chính:
 Phần mở đầu ứng dụng:

Trong hoạt động này, người sử dụng có 2 lựa chọn như sau:
 Nhấn vào hình ảnh đại diện lớn của nhóm ở giữa màn hình để
đi tới hoạt động tải bức ảnh lên chính sửa
 Nhấn vào dòng chữ:”For more information about us”, 1 hộp
tin nhắn sẽ hiện lên như sau:”If you keep this action, the editor
application will be paused”. Tới đây, người dùng sẽ có 2 lựa
chọn. Nếu ấn cancel, thì người dùng sẽ trở lại màn hình mở
đầu của ứng dụng. Nếu ấn OK, người dùng sẽ di chuyển tới
blogger của nhóm.

 Màn hình tải bức ảnh để chỉnh sửa:

6
Trong hoạt động này, người dùng sẽ nhìn thấy một bức ảnh lớn ở
chính giữa với dòng chữ:”Chọn ảnh đi”. Người dùng ấn vào bức ảnh
này, sẽ có một hộp tin nhắn hiện lên, yêu cầu người dùng sử dụng
thực hiện thao tác chọn gallery. Cuối cùng người dùng chọn 1 bức
ảnh.

 Màn hình chỉnh sửa ảnh:

7
Trong hoạt động chỉnh sửa ảnh, chúng tôi có cung cấp 17 các mẫu lọc
màu cho bức ảnh: Normal, Struck, Clarendon, OldMan, Mars, Rise,
April, Amazon, Starlit, Whisper, Lime, Haan, BlueMess, Adele, Cruz,
Metropolis, Audrey. Sau khi chọn 1 trong các lọc màu sắc trên, người
dùng có thể lưu trữ lại bức ảnh bằng cách ấn vào nút SAVE bên góc
phải trên cùng (Người dùng cần chú ý, bức ảnh sẽ được lưu trữ vào
chính bức ảnh ban đầu). Nếu bức ảnh có thể save được, sẽ có 1 dòng
chữ hiện lên ở phía cuối màn hình:”Image saved to gallery”. Bên cạnh
dòng chữ này, có 1 chữ OPEN màu đỏ. Nhấn vào nút này để đi tới
gallery, nơi bức ảnh vừa sửa được lưu trữ. Nếu bức ảnh không thể lưu
trữ, sẽ có một dòng chữ hiện lên ở phía cuối màn hình là:”Unable to
save image!”.
Sau đó, người dùng có thể tiếp tục hoạt động chỉnh sửa ảnh bằng cách
ấn vào bức ảnh to ở chính giữa màn hình để load 1 bức ảnh khác.

2. Đối với người phát triển


Ứng dụng chỉnh sửa ảnh do V_CH group xây dựng và phát triển
trên nền tảng phần mềm Android Studio. Ngôn ngữ sử dụng là
JAVA. Chính vì vậy người phát triển cần cài đặt phần mềm Android
Studio trên máy tính của mình. Link tải phần mềm này được đính
kèm sau đây: https://developer.android.com/studio. Sau đó, cần
tải và cài đặt JDK trên máy tính từ trang web sau:
https://www.oracle.com/technetwork/java/javase/downloads/index.h
tml. Về phần thiết bị để chạy ứng dụng, người dùng có 2 lựa chọn.
Lựa chọn 1 là sử dụng Virtual device. Để cài đặt Virtual device,
người phát triền có thể tham khảo cách cài đặt sau đây:
https://www.youtube.com/watch?v=Wx4KWPZhSHc. Lựa chọn 2 là sử
dụng thiết bị di động của chính người phát triển. Người phát triển
lựa chọn cách này thì có thể tham khảo link dưới đây:
https://www.youtube.com/watch?v=p2oHD-06YcM

Mỗi ứng dụng android đều gồm 2 phần chính: user interface hay
UI (được hiển thị bằng file.xml) và code cho từng user interface.
Trong ứng dụng chỉnh sửa ảnh này, chúng tôi sử dụng ngôn ngữ
JAVA. Chính vì vậy, để có thể hiểu được phần lập trình cho ứng
dụng này, người đọc cần có những hiểu biết cơ bản về ngôn ngữ lập
trình JAVA và ngôn ngữ xml.
Về ngôn ngữ xml cho thiết kế giao diện, chúng tôi tham khảo
nhiều trên trang web:
https://www.tutorialspoint.com/android/index.htm.
Về ngôn ngữ JAVA, người phát triển có thể đọc qua sách:
Beginning Android® Programming with Android Studio 4th Edition
(J.F.DiMarzio)
Để lập trình và thiết kế cho hệ thống này, cần thêm vào
dependencies thuộc build.gradle (Module app):

8
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.karumi:dexter:4.1.0'
implementation 'info.androidhive:imagefilters:1.0.7'

Imagefilter, được chỉnh sửa từ thư viện AndroidHive, trong ứng


dụng này, được sử dụng để sử dụng các công cụ color filters
Dexter dùng để request runtime permission và tải ảnh, lưu ảnh
dễ dàng hơn

a) Thiết kế hệ thống

b) Thiết kế giao diện


i. Phác thảo logo bằng công cụ vẽ trên máy tính
Logo của app và nhóm:

9
ii. Thiết kế giao diện trong android studio
 Các view được sử dụng trong app:
- TextView: nhiệm vụ chính của TextView là hiển thị
dòng văn bản trên màn hình dung nhằm cung cấp
thông tin cho người dùng. Người lập trình cũng có
thể thay đổi nội dung dòng văn bản này bằng cách sử
dụng câu lệnh android:text= “information”.
- ImageView: là view dùng để hiện thị hình ảnh lên
màn hình sử dụng. Các hình ảnh này có thể là tài
nguyên lưu trữ trong app hay các hình ảnh tải xuống
từ bộ nhớ máy hay internet.
- Button: button tiếp nhận hành động click của người
dùng và thực hiện một thao tác đã được lập trình từ
trước đó.
- Các View Group: Recyclerview, Toolbar,
LinearLayout, ConstraintLayout, RelativeLayout,
CoordinatorLayout.
 Layout: Các layout chính là các View (cụ thể nó kế thừa
thừa ViewGroup) được thiết kế với mục đích chứa các
View con và điều khiển, sắp xếp vị trí các View con đó
trên màn hình, mỗi layout có cơ chế điều khiển vị trí
View con riêng của mình. Các layout đã được thiết kế
trong app:
Activity_starting.xml: là một ConstraintLayout cho
phép điều chỉnh vị trí và ứng sử của các view bằng

10
cách dàng buộc đơn giản vào mỗi view con. Trong
activity_starting.xml chúng tôi thiết kế 2 view con là
textview và imageview. Textview này dùng để hiển
thị dòng văn bản: “for more information about us”.
Imageview này dùng để hiển thị logo của app.

- Activity_loading.xml: là một CoordinatorLayout nó


được thiết kế nhằm mục đích có sự tương tác của các
view con trong nó. Trong activity_loading.xml có
chứa 2 layout con là appbarlayout và layout_content.
Trong appbarlayout sử dụng toolbar có chứa 2
button và một textview, một button với chức năng
điều hướng button còn lại thực hiện chức năng save
ảnh, textview hiển thị dòng văn bản VCH_Filter.
- Layout_content.xml: là một RelativeLayout cho
phép sắp xếp các view con ở bất cứ vị trí nào mà
người thiết kế muốn. Như đã nói ở trên lay_content
là layout con của activity_loading.xml trong đó nó
có chứa 3 view con là imageview,
NonSwipeableViewPager và tablayout.
- Fragment_filter_list.xml: là một FrameLayout nó
cung cấp một vùng màn hình và hiển thị một view
con duy nhất là Recyclerview. Recyclerview này
hiển thị một tập các item gồm các màu sắc khác
nhau khi lọc ảnh. Nó cho phép thao tác scroll màn
hình theo chiều ngang.
 Thumbnail_item.xml: là một LinearLayout cho phép
các view con sắp xếp nối tiếp nhau theo cột. Nó gồm
2 view con là textview dùng để hiển thị dòng text:
“filter name” và một imageview.
iii. Sử dụng values trong resources
Việc sử dụng Values trong thiết kế giao diện của ứng dụng là
tất yếu. Việc sử dụng Resources làm cho việc thiết kế trở nên rõ
ràng hơn cho người phát triển. Chính vì vậy, để có một phần thiết
kế hoàn chỉnh, người thiết kế cần sử dụng cả 3 mục: colors,
strings, styles. Các màu sắc, dòng chữ và các styles dùng trong
thiết kế cần lưu trữ trong các mục cùng tên. Chẳng hạn, trong dự
án này có dòng chữ:”For more information about us”, cần lưu trữ
vào trong mục Strings, để người phát triển sau có thể nắm rõ được
các dòng chữ hiển thị trên giao diện của người dùng.

c) Lập trình cho hệ thống


i. Các class có trong hệ thống
Hệ thống được xây dựng bởi 2 lớp chính:

11
 Starting Activity: Trong lớp này xử lý 2 nút là
startingButton và informationButton. Núi startingButton
sử dụng Intent để dẫn sang LoadingActivity. Nút
informationButton, cũng sử dụng Intent, chuyển người
dùng đến blogger của nhóm. Bên cạnh đó, sau khi người
dùng nhấn vào nút informationButton này, sẽ có 1 dòng
gạch chân dưới dòng chữ “For more information about
us”. Để thực hiện điều này, chúng tôi sử dụng 1 phương
thúc của TextView là setPaintFlags. Thêm nữa, cũng trong
informationButton, AlertDialog.Builder được thêm vào,
nhằm mục đích mở ra 1 tin nhắn cho người dùng: nếu tiếp
tục sẽ dừng lại ứng dụng. Khi đoạn nhắn hiện lên, để có 2
sự lựa chọn là “OK” hoặc “Cancel” cho người sử dụng,
cần dùng lần lượt đến setPositiveButton và
setNegativeButton của AlertDialog.Builder. Cuối cùng,
tạo AlertDialog và dùng hàm show() của AlertDialog để
hiện thị hộp thoại
 Loading Activity: đây là nơi chúng tôi đã xử lý phần tải
bức ảnh để chỉnh sửa, chỉnh sửa bức ảnh và lưu trữ lại bức
ảnh sau khi chỉnh sửa.
- Tải bức ảnh lên để chỉnh sửa: Chúng tôi không sử dụng
cách thông thường là dùng button để tải bức ảnh lên, thay
vào đó, chúng tôi sử dụng “ImageView”. Để truyền bức
ảnh với dòng chữ: “Chọn ảnh đi” (loading_image) vào
ImageView, chúng tôi viết hàm loadImage(). Trong hàm
này, tôi sử dụng 3 thuộc tính kiểu trả về Bitmap:
originalBitmap, filterBitmap, finalBitmap. Thuộc tính giá
trị của originalBitmap (để tránh trường hợp bức
loading_image không bị crash trong quá trình tải lên, chúng
tôi có viết lớp BitmapUtils với phương thức
getBitmapFromAssets và Asset là nơi chứa bức
loading_image. Phương thức này cho phép lấy bức ảnh, rồi
crop nó theo độ dài và độ rộng cho trước) sẽ được truyền
vào thuộc tính img_preview, được sử dụng như 1 nút để tải
bức ảnh. 2 thuộc tính kiểu trả về Bitmap còn lại được dùng
trong trường hợp người dùng lưu trữ chính loading_image.
Khi đó, sẽ không có lỗi xảy ra.
Sau khi truyền được bức ảnh loading_image vào
img_preview, chúng tôi tiến tới truyền khả năng tải ảnh vào
img_preview. Các hàm cần viết thêm BitmapUtils (sử dụng
phương thức getBitmapFromGallery). Trong lớp
LoadingActivity, có 1 phương thức là loadImage. Đây là
phương thức xử lí “onClick” của image_preview trong
layout_content.xml. Để load được ảnh, cần phương thức
openImageFromGallery trong hàm cùng tên. Hàm này xử

12
dụng thư viện Dexter để dễ dàng tải ảnh lên. Để có thể tải
ảnh lên, cần quyền truy cập vào bộ nhớ của thiết bị:
READ_EXTERNAL_STORAGE và
WRITE_EXTERNAL_STORAGE. Nếu người dùng không
cấp quyền truy cập, 1 dòng text, nội dung là Permission,
được hiện lên nhờ sử dụng Toast.makeText. Ngược lại,
Intent sẽ giúp chúng ta tải bức ảnh lên và chúng ta sử dụng
startActivityForResult. Sau đó, tại phương thức
onActivityResult, các originalBitmap, filterBitmap,
finalBitmap, sẽ cập nhật bitmap của bức ảnh vừa được tải
và cuối cùng, img_preview nhận bức ảnh này:
img_preview.setImageBitmap(originalBitmap) trước khi
các bức ảnh con ở filter màu sắc dưới cũng đồng loạt
update lại bức ảnh này.
- Chỉnh sửa bức ảnh: phục vụ cho chỉnh sửa ảnh, chúng tôi
đã viết interface: FilterListFragmentListener và các lớp:
ThumbnailAdapter, ViewPageAdapter và
FilterListFragment. ViewPageAdapter chúng tôi đã giới
thiệu ở mục b) thiết kế giao diện cho hệ thống. Tại lớp
FilterListFragment, phương thức setListener sử dụng
phương thức trong interface FilterListFragmentListener để
tạo default cho filter màu sắc, ngay sau khi người dùng vào
hoạt động chỉnh sửa ảnh. Bên cạnh đó, phương thức
onCreateView được tạo. Khi biến kiểu trả về
FilterListFragment được tạo ở LoadingActivity
(setupViewPager), onCreateView được chạy, truyền các
filter màu sắc của thư viện AndroidHive với sự hỗ trợ của
phương thức DisplayThumbnail trong cùng lớp và của lớp
ThumbnailAdapter. Sau đó, bức ảnh vừa được tải lên sẽ
được truyền vào các ảnh con của các filter này. Nếu như
bức ảnh được tải lên có giá trị Bitmap là null thì
loading_image sẽ được thay thế.
Trong lớp LoadingActivity, tại phương thức
onFilterSelected, khi 1 filter màu sắc được chọn, thuộc tính
filterBitmap sẽ thay đổi màu sắc: filterBitmap=
originalBitmap.copy(Bitmap.Config.ARGB_8888, true) và
img_preview và finalBitmap sẽ cập nhật sự thay đổi này.
- Lưu trữ bức ảnh: Chúng tôi thực hiện việc lưu trữ bức
ảnh nhờ vào thư viện Dexter. Trước đó, chúng tôi cũng có
yêu cầu người dùng cho các quyền đọc và viết vào bộ nhớ
của thiết bị:
Dexter.withActivity(this)
.withPermissions(Manifest.permission.READ_E
XTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE).

13
Nếu người dùng không cấp quyền truy cập, 1 dòng text, nội
dung là Permission, được hiện lên nhờ sử dụng
Toast.makeText
Bắt đầu tại lớp LoadingActivity, ở phương thức
onOptionsItemSelected, sau khi người dùng ấn vào nút
save, phương thức saveImage() được gọi. Sau khi các
quyền truy cập vào bộ nhwos thiết bị được đáp ứng, với sự
trợ giúp của phương thức insertImage tại hàm BitmapUtils
chúng tôi đã đề cập bên trên, bức ảnh được lưu lại:
final String path =
BitmapUtils.insertImage(getContentResolver(),
finalBitmap, System.currentTimeMillis() + "_profile.jpg",
null);
Tuy nhiên, ở đây có 2 trường hợp xảy ra: bức ảnh có thể
lưu lại và không thể lưu lại bức ảnh.
Trường hợp không thể lưu lại bức ảnh xảy ra khi bộ nhớ
của thiết bị đã đầy. Chính vì vậy, cần có 1 báo hiệu cho
người dùng:
Snackbar snackbar = Snackbar.make(coordinatorLayout,
"Unable to save image!", Snackbar.LENGTH_LONG);
Trường hợp còn lại, khi bức ảnh đã được lưu lại, sẽ có một
dòng text thể hiện bức ảnh đã được lưu lại:
Snackbar snackbar = Snackbar.make(coordinatorLayout,
"Image saved to gallery!",
Snackbar.LENGTH_LONG).setAction("OPEN", new
View.OnClickListener(){ @Override
public void onClick(View v) {
openImage(path);
}
});
Bên cạnh dòng chữ này, để có 1 link dẫn đến bức ảnh vừa
lưu, chúng tôi đã viết thêm phương thức openImage. Tại
phương thức này, chúng tôi sử dụng intent để đến nơi lưu
trữ bức ảnh.

ii. Các bẫy lỗi đã sử dụng


Trong quá trình xây dựng ứng dụng này, nhóm V_CH group
chủ yếu đặt các bẫy lỗi khi truyền bức ảnh loading_image vào
image_preview, tải bức ảnh lên hoặc khi lưu trữ bức ảnh sau khi
chỉnh sửa.
Bẫy lỗi khi truyền bức ảnh loading_image vào
image_preview. Dạng bẫy lỗi sử dụng: IOException. Bẫy lỗi đặt ở
đây phòng trường hợp người phát triển quên không đưa
loading_image vào assets file. Khi ấy, hệ thống sẽ in ra chỗ bị sai
e.printStackTrace();

14
Bẫy lỗi khi tải bức ảnh chỉnh sửa. Lỗi xảy ra khi người dùng
không cung cấp quyền truy cập vào bộ nhớ thiết bị. Ở đây, chúng
tôi sử dụng bẫy lỗi dưới dạng mệnh đề if else. Khi lỗi xảy ra, hệ
thống in ra báo hiệu:
Toast.makeText(LoadingActivity.this, "Permission",
Toast.LENGTH_SHORT).show();
Bẫy lỗi khi lưu trữ bức ảnh sau chỉnh sửa. Tương tự khi tải
bức ảnh lên, nếu người dùng không cung cấp quyền truy cập thì
hệ thống in ra cảnh báo tương tự. Bên cạnh đó, còn có lỗi khi nào
bộ nhớ của thiết bị đã đầy, không còn chỗ trống để lưu trữ bức
ảnh mới được chỉnh sửa

d) Đánh giá
Như đã đề cập tại phần mục tiêu, khó khăn và cách tiếp cận, ban
đầu, chúng tôi đã đề ra 3 mục tiêu cần phải thực hiện:
 Ra được 1 ứng dụng chỉnh sửa ảnh trên điện thoại với các công cụ
cơ bản.
 Ứng dụng có tốc độ nhanh và dung lượng nhẹ.
 Các thành viên cùng thực hiện dự án, hiểu được dự án và có khả
năng trình bày về công việc của mình khi dự án kết thúc.
Hiện nay, chúng tôi đã hoàn thành được mục tiêu 1 và 3, tương
ứng với 66% mục tiêu đã đề ra. Nhóm đã hoàn thành được 1 công cụ
chỉnh sửa ảnh trên điện thoại và demo được trong buổi trình bày cuối
cùng. Cũng trong buổi này, cả 2 thành viên hiện tại còn hoạt động đều
hiểu được và trình bày được rõ ràng về những phần công việc mà
mình đã nhận và hoàn thành.
Mục tiêu thứ 2 là xây dựng ứng dụng có tốc độ nhanh và dung
lượng nhẹ, nhóm sẽ tiếp tục hoàn thiện trong thời gian sắp tới.
e) Định hướng phát triển
Trong thời gian tới, mục tiêu của nhóm là tối ưu hóa tốc độ của
phần mềm và cải thiện tốc độ.
Bên cạnh đó, trong quá trình 3 tháng vừa rồi, nhóm cũng nghiên
cứu những công cụ cơ bản trong chỉnh sửa ảnh như crop, rotate và
phần setting cho ứng dụng. Chính vì vậy, việc tích hợp 3 công cụ này
cũng sẽ nằm trong phần phát triển trong thời gian tới.
Tách hoạt động chỉnh sửa ảnh ra khỏi hoạt động tải bức ảnh lên là
1 trăn trở của những người xây dựng công cụ này. Đây sẽ là 1 mục
tiêu rất quan trọng trong thời gina sắp tới. Không chỉ giúp ứng dụng
trở nên chuyên nghiệp hơn, việc tách 2 hoạt động này cũng làm tăng
sự hiểu biết về lớp Bitmap, 1 công cụ đắc lực trong Android Studio.
Hoàn thành những công việc chuyên môn đã đề ra trong phần này
đòi hỏi cải thiện cách làm việc của từng thành viên. Trong đó, cần
phân chia những công việc phù hợp hơn với khả năng của từng người.
Thêm nữa, các thành viên cũng cần có trách nhiệm hơn với phần việc
mà mình đã nhận, hoàn thành đúng hạn. Tuy vậy, cần tránh trường

15
hợp làm việc qua loa, đối phó để hoàn thành đúng hạn. Nếu không thể
hoàn thành đúng hạn, cần báo cáo lại với nhóm trước 1 ngày định
trước để cả nhóm có thể sớm giải quyết trục trặc này.
Cuối cùng, cần đẩy mạnh hơn trong cập nhật tiến độ làm việc và
mục tiêu trên blogger của nhóm. Bởi lẽ, sẽ có những công việc mà
nhóm không thể làm được. Khi ấy, để giải quyết thắc mắc, giáo viên
hướng dẫn cần biết tiến độ, chỗ vướng mắc, mà ở đây, blogger là
công cụ hiệu quả để cập nhật cho 1 người ngoài không nằm trong
nhóm.

III. Thảo luận


1) Cách làm việc nhóm
- Trong quá trình làm việc, chúng tôi thấy, nếu sắp xếp được thời
gian để gặp gỡ và trao đổi với các thành viên trong nhóm ngay trước
và sau buổi báo cáo định kì sẽ thúc đẩy tính gắn kết giữa các thành
viên. Thêm nữa, các buổi họp này cũng làm cho các thành viên
trong nhóm hiểu hơn về chính dự án, tiến độ công vệc, các khúc
mắc của từng thành viên đang có với công việc hiện tại của mình.
Đây là tiền đề cho các cách giải quyết đến với từng vấn đề va chạm
phải trong quá trình làm việc.
- Làm việc qua mạng là 1 trong những giải pháp để giải quyết vấn
đề sắp xếp thời gian, khi các thành viên không thể đến được buổi
họp.
- Khi có mâu thuẫn xảy ra giữa các thành viên, cần trao đổi trực tiếp
với nhau, tránh tình trạng giữ sự bực bội trong người mà làm ảnh
hưởng đến kết quả của công việc. Chẳng hạn, việc so bì khối lượng
công việc người này với người khác là mẫu thuẫn thường xảy ra
trong khi làm việc nhóm. Khi ấy, cần trao đổi trực tiếp ngay để các
thành viên đều cảm thấy công bằng và hài lòng với phần việc mình
nhận.
2) Phát triển hệ thống
- Để phục vụ cho mục tiêu ban đầu là tốc độ và dung lượng của
ứng dụng, cần thiết thiết kế một thư viện riêng, chỉ bao gồm những
công cụ sử dụng trong ứng dụng. Trong ứng dụng chúng tôi đã hoàn
thiện, thư viện androidHive đã được đưa vào. Bên cạnh những công
cụ chỉnh sửa ảnh rất hữu hiệu của thư viện được đưa vào dự án, cũng
có những công cụ không được sử dụng như crop, add image,… Chính
điều này đã làm tăng dung lượng của ứng dụng. Hay việc để quá
nhiều phương thức trong 1 lớp như lớp loadingActivity chúng tôi đã
làm cũng làm giảm tốc độ khi activity này được chạy. Để giải quyết
việc này, nhất thiết cần tách hoạt động chỉnh sửa ảnh và hoạt động tải
bức ảnh cần chỉnh sửa.

IV. Kết Luận

16
Sau 3 tháng hoạt động, ứng dụng đã được xây dựng và phát triển gần
như kì vọng. Tuy vậy, chúng tôi cũng cần nỗ lực nhiều hơn nữa để hoàn
thiện ứng dụng chỉnh sửa ảnh này, phục vụ tốt hơn cho người dùng.
Cuối cùng, xin một lần nữa cảm ơn các thành viên trong nhóm đã
cống hiến hết mình trong suốt thời gian quá. Xin phép được cảm ơn thầy
giáo Vũ Hải đã giúp đỡ chúng em trong quá trình xây dựng ứng dụng
này.

Main:
package com.example.v_chproject;

import android.Manifest;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import com.example.v_chproject.Adapter.ViewPageAdapter;
import com.example.v_chproject.Interface.FilterListFragmentListener;
import com.example.v_chproject.Utils.BitmapUtils;
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.io.IOException;
import java.util.List;

public class LoadingActivity extends AppCompatActivity implements FilterListFragmentListener{

public static final String pictureName = "loading_image.png";


public static final int PERMISSION_PICK_IMAGE = 1000;

ImageView img_preview;
TabLayout tabLayout;
ViewPager viewPager;
CoordinatorLayout coordinatorLayout;

Bitmap originalBitmap, filterBitmap, finalBitmap;

FilterListFragment filterListFragment;

/*
load native image filters library
*/
static {
System.loadLibrary("NativeImageProcessor");
}

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

Toolbar toolbar = findViewById(R.id.toolBar);


setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle("V_CH Filter");

img_preview = (ImageView) findViewById(R.id.image_preview);

17
tabLayout = (TabLayout) findViewById(R.id.tabs);
viewPager = (ViewPager) findViewById(R.id.viewPager);
coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator);

loadImage();

setupViewPager(viewPager);
tabLayout.setupWithViewPager(viewPager);

private void loadImage() {


originalBitmap = BitmapUtils.getBitmapFromAssets(this, pictureName, 300, 300);
filterBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true);
finalBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true);
img_preview.setImageBitmap(originalBitmap);
}

private void setupViewPager(ViewPager viewPager) {


ViewPageAdapter adapter = new ViewPageAdapter(getSupportFragmentManager());

filterListFragment = new FilterListFragment();


filterListFragment.setListener(this);

adapter.addFragment(filterListFragment, "Filters");

viewPager.setAdapter(adapter);
}

@Override
public void onFilterSelected(Filter filter) {
filterBitmap= originalBitmap.copy(Bitmap.Config.ARGB_8888, true);
img_preview.setImageBitmap(filter.processFilter(filterBitmap));
finalBitmap = filterBitmap.copy(Bitmap.Config.ARGB_8888, true);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return super.onCreateOptionsMenu(menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_save) {
saveImage();
return true;
}
return super.onOptionsItemSelected(item);
}

private void saveImage() {


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()) {
try {
final String path = BitmapUtils.insertImage(getContentResolver(),
finalBitmap, 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 v) {
openImage(path);
}
});
snackbar.show();
} else {
Snackbar snackbar = Snackbar.make(coordinatorLayout, "Unable
to save image!", Snackbar.LENGTH_LONG);
snackbar.show();
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
Toast.makeText(LoadingActivity.this, "Permission",
Toast.LENGTH_SHORT).show();
}
}

18
@Override
public void onPermissionRationaleShouldBeShown(List<PermissionRequest>
permissions, PermissionToken token) {
token.continuePermissionRequest();
}
}).check();
}

private void openImage(String path) {


Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(path), "image/*");
startActivity(intent);
}

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, PERMISSION_PICK_IMAGE);
} else {
Toast.makeText(LoadingActivity.this, "Permission",
Toast.LENGTH_SHORT).show();
}
}

@Override
public void onPermissionRationaleShouldBeShown(List<PermissionRequest>
permissions, PermissionToken token) {
token.continuePermissionRequest();
}
}).check();
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (resultCode == RESULT_OK && requestCode == PERMISSION_PICK_IMAGE) {
Bitmap bitmap = BitmapUtils.getBitmapFromGallery(this, data.getData(), 800, 800);

originalBitmap.recycle();
finalBitmap.recycle();
filterBitmap.recycle();

originalBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);


finalBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true);
filterBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true);
img_preview.setImageBitmap(originalBitmap);
bitmap.recycle();

filterListFragment.displayThumbnail(originalBitmap);
}
}

public void loadingImage(View view) {


openImageFromGallery();
}
}

19

You might also like