บทเรียนภาคเสริมสำหรับผู้ที่ต้องการพัฒนาแอปพลิเคชันอ่านการ์ตูนออนไลน์ด้วย Android และ Firebase แต่เปลี่ยนจาก PDF เป็น Image List แทน
ศึกษาบทความก่อนหน้า: Android Studio กับการทำระบบ Bookshelf ร่วมกับ Firebase เบื้องต้น ตอนที่ 1
เราจะได้หน้าจอการทำงานของแอปพลิเคชันเราดังนี้:
ซึ่งในบทความตอนที่ 2 (Link: Android Studio กับการทำระบบ Bookshelf ร่วมกับ Firebase เบื้องต้น ตอนที่ 2) เราจะใช้ Firebase จัดการ Storage ที่เก็บ Path Location ของไฟล์ PDF มาแสดงผลด้วย Google Docs API ผ่าน WebView แต่ในบทความนี้ถือว่าเป็นภาคแยกแทน ซึ่งจะเปลี่ยนโครงสร้าง Firebase ใหม่ให้เป็น Image List หรือ ภาพ Jpeg หรือ Png ทีละภาพเก็บบน Firebase
ดังนั้น:
ลืมบทความตอน 2 เสีย ถ้าใครต้องการทำ comic แบบภาพหลายๆ ภาพเป็นต้น
เริ่มต้นให้เราเพิ่มรูปภาพ Comic แต่ละหน้าเข้าไปยัง Firebase ให้เป็นโครงสร้างดังนี้:
ใส่ใน Key00002 ในตัวอย่าง สัก 4 หน้า โครงสร้าง JSON จะเป็นแบบนี้: https://enet5-7f9f6.firebaseio.com/bookshelf/data/key00002.json
ไปที่ Android Studio ให้ทำการเพิ่ม Empty Activity ใหม่ขึ้นมาว่า ComicActivity.java
ให้ออกแบบหน้าจอ Layout ของ activity_comic.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=".ComicActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recycleComic" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginBottom="1dp" android:layout_marginEnd="1dp" android:layout_marginLeft="1dp" android:layout_marginRight="1dp" android:layout_marginStart="1dp" android:layout_marginTop="1dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout>
ต่อมาให้เราไปที่ DataModel.java ให้เขียนแก้ไข Class จากบทเรียนตอนที่ 1 ใหม่ให้เป็นแบบนี้:
package com.daydev.firecomic; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; public class DataModel { String title,desc,photos,files,photo,key; public DataModel(){ } public DataModel(String title, String desc, String photos,String files, String key, String photo) { this.title = title; this.desc = desc; this.photos = photos; this.files = files; this.key = key; this.photo = photo; } public Map<String, Object> toMap(){ HashMap<String, Object> result = new HashMap<>(); result.put("title",title); result.put("content",desc); result.put("photos",photos); result.put("files",files); result.put("photo",photo); return result; } }
เพิ่ม photo เข้าไป (อย่าสัปสนกับ photos) ตัว photo นี้เอาไว้เรียก Key ของ ภาพการ์ตูนแต่ละหน้า ส่วน photos คือภาพเปิดของหน้าปกภาพเดียว (ลองเทียบกับ โครงสร้าง firebase ข้างบน, ขออภัยที่ตั้งตัวแปรให้งงสำหรับมือใหม่ไก่อ่อน)
ไปที่ ComicActivity.java ให้ทำการเพิ่มตัวแปรต่อไปนี้เป็น Global
private static final String TAG = "FireComic"; private List<DataModel> response_data; private ComicDataAdapter dataAdapter; private RecyclerView mRecyclerView; private String get_key; private FirebaseDatabase firebaseDatabase; private DatabaseReference databaseReference;
ระบบจะทำการบังคับให้เราสร้าง Class ใหม่ชื่อ ComicDataAdapter.java ขึ้นมา(กด Alt+Enter)
ให้ทำการสร้าง Class ชื่อ ComicDataAdapter.java เสีย หลังจากนั้นให้แก้ไขไฟล์นี้โดยอ้างอิงบทเรียนที่ 1 ใหม่อีกที:
package com.daydev.firecomic; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import com.squareup.picasso.Picasso; import java.util.List; class ComicDataAdapter extends RecyclerView.Adapter<ComicDataAdapter.DataViewHolder>{ private List<DataModel> dataModelList; private DataModel dataModel; public ComicDataAdapter(List<DataModel> result) { this.dataModelList = result; } @NonNull @Override public ComicDataAdapter.DataViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { return new ComicDataAdapter.DataViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.pages,viewGroup,false)); } @Override public void onBindViewHolder(@NonNull ComicDataAdapter.DataViewHolder dataViewHolder, int i) { dataModel = dataModelList.get(i); Picasso.get().load(dataModel.photo) .error(R.mipmap.ic_launcher) .placeholder(R.mipmap.ic_launcher) .into(dataViewHolder.imageView); } @Override public int getItemCount() { return dataModelList.size(); } public static class DataViewHolder extends RecyclerView.ViewHolder { ImageView imageView; public DataViewHolder(final View itemView) { super(itemView); imageView = itemView.findViewById(R.id.imageView); } } }
ระบบจะทำการบังคับให้เราสร้าง Layout ใหม่ขึ้นมาชื่อว่า pages.xml
แก้ไข Layout ของ pages.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"> <ImageView android:id="@+id/imageView" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:scaleType="fitCenter" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/ic_launcher_background" /> </android.support.constraint.ConstraintLayout>
เมื่อเสร็จเรียบร้อยแล้วให้เปิดไฟล์ DataAdapter.java ขึ้นมา แก้ไขส่วนของ onBindViewHolder() ให้เป็นดังนี้:
@Override public void onBindViewHolder(final DataViewHolder dataViewHolder, final int i) { final ArrayList<String> mylist = new ArrayList<String>(); dataModel = dataModelList.get(i); dataViewHolder.textTitle.setText(dataModel.title); databaseReference = FirebaseDatabase.getInstance().getReference(); databaseReference.child("bookshelf/data").addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { Log.d(TAG, "Count= " +dataSnapshot.getChildrenCount()); for (DataSnapshot childDataSnapshot : dataSnapshot.getChildren()) { Log.d(TAG, "snapshot= " + childDataSnapshot.getKey()); mylist.add(childDataSnapshot.getKey()); } } @Override public void onCancelled(DatabaseError databaseError) { } }); Picasso.get().load(dataModel.photos) .error(R.mipmap.ic_launcher) .placeholder(R.mipmap.ic_launcher) .into(dataViewHolder.imageView); dataViewHolder.imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String filePath = dataModel.files; Log.d(TAG, "filePath=" + filePath); Intent readActivity = new Intent(v.getContext(),ComicActivity.class); readActivity.putExtra("filePath",filePath); readActivity.putExtra("keys", mylist.get(i)); v.getContext().startActivity(readActivity); } }); }
เป็นการดึง DataSnapshot ของ Firebase ทั้งหมดมาวน Foreach เพื่อเก็บค่า Key ของข้อมูลเราสำหรับนำไปอ้างเพื่อส่ง Intent โดยตัวอย่างของผมคือการเก็บลง ArrayList ชื่อ mylist
final ArrayList<String> mylist = new ArrayList<String>(); dataModel = dataModelList.get(i); dataViewHolder.textTitle.setText(dataModel.title); databaseReference = FirebaseDatabase.getInstance().getReference(); databaseReference.child("bookshelf/data").addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { Log.d(TAG, "Count= " +dataSnapshot.getChildrenCount()); for (DataSnapshot childDataSnapshot : dataSnapshot.getChildren()) { Log.d(TAG, "snapshot= " + childDataSnapshot.getKey()); mylist.add(childDataSnapshot.getKey()); } } @Override public void onCancelled(DatabaseError databaseError) { } });
หลังจากนั้นจึงใช้ Position ที่เป็นตัวแปร i ในการอ้างอิงส่งตำแหน่งของ ArrayList ที่เลือกส่งผ่าน Intent ไป
dataViewHolder.imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String filePath = dataModel.files; Log.d(TAG, "filePath=" + filePath); Intent readActivity = new Intent(v.getContext(),ComicActivity.class); readActivity.putExtra("filePath",filePath); readActivity.putExtra("keys", mylist.get(i)); v.getContext().startActivity(readActivity); } });
ดังนั้นไฟล์ DataAdapter.java จะเป็นดังนี้:
package com.daydev.firecomic; import android.content.Intent; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.database.ValueEventListener; import com.squareup.picasso.Picasso; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class DataAdapter extends RecyclerView.Adapter<DataAdapter.DataViewHolder> { private static final String TAG = "FireComic"; private List<DataModel> dataModelList; private DataModel dataModel; private DatabaseReference databaseReference; public DataAdapter(List<DataModel> result) { this.dataModelList = result; } @Override public DataAdapter.DataViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { return new DataViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.content_read,viewGroup,false)); } @Override public void onBindViewHolder(final DataViewHolder dataViewHolder, final int i) { final ArrayList<String> mylist = new ArrayList<String>(); dataModel = dataModelList.get(i); dataViewHolder.textTitle.setText(dataModel.title); databaseReference = FirebaseDatabase.getInstance().getReference(); databaseReference.child("bookshelf/data").addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { Log.d(TAG, "Count= " +dataSnapshot.getChildrenCount()); for (DataSnapshot childDataSnapshot : dataSnapshot.getChildren()) { Log.d(TAG, "snapshot= " + childDataSnapshot.getKey()); mylist.add(childDataSnapshot.getKey()); } } @Override public void onCancelled(DatabaseError databaseError) { } }); Picasso.get().load(dataModel.photos) .error(R.mipmap.ic_launcher) .placeholder(R.mipmap.ic_launcher) .into(dataViewHolder.imageView); dataViewHolder.imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String filePath = dataModel.files; Log.d(TAG, "filePath=" + filePath); Intent readActivity = new Intent(v.getContext(),ComicActivity.class); readActivity.putExtra("filePath",filePath); readActivity.putExtra("keys", mylist.get(i)); v.getContext().startActivity(readActivity); } }); } @Override public int getItemCount() { return dataModelList.size(); } public static class DataViewHolder extends RecyclerView.ViewHolder { TextView textTitle; ImageView imageView; public DataViewHolder(final View itemView) { super(itemView); textTitle = itemView.findViewById(R.id.title); imageView = itemView.findViewById(R.id.thumbnail); } } }
กลับไปยัง ComicActivity.java ให้เพิ่มคำสั่งต่อไปนี้ใน onCreate()
Intent intent= getIntent(); get_key = intent.getStringExtra("keys"); firebaseDatabase = FirebaseDatabase.getInstance(); databaseReference = firebaseDatabase.getReference("bookshelf/data/"+get_key+"/pages"); Log.d(TAG, "bookshelf/data/"+get_key+"/pages"); response_data = new ArrayList<>(); LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); mRecyclerView = (RecyclerView) findViewById(R.id.recycleComic); mRecyclerView.setLayoutManager(layoutManager); dataAdapter = new ComicDataAdapter(response_data); mRecyclerView.setAdapter(dataAdapter); comicBindingData();
เพิ่มคำสั่งในเมธอดใหม่ comicBindingData()
private void comicBindingData() { databaseReference.addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String s) { response_data.add(dataSnapshot.getValue(DataModel.class)); dataAdapter.notifyDataSetChanged(); } @Override public void onChildChanged(DataSnapshot dataSnapshot, String s) { } @Override public void onChildRemoved(DataSnapshot dataSnapshot) { } @Override public void onChildMoved(DataSnapshot dataSnapshot, String s) { } @Override public void onCancelled(DatabaseError databaseError) { } }); }
ดังนั้นคลาสของ ComicActivity.java จะเป็นดังนี้:
package com.daydev.firecomic; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import com.google.firebase.database.ChildEventListener; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.database.ValueEventListener; import java.util.ArrayList; import java.util.List; public class ComicActivity extends AppCompatActivity { private static final String TAG = "FireComic"; private List<DataModel> response_data; private ComicDataAdapter dataAdapter; private RecyclerView mRecyclerView; private String get_key; private FirebaseDatabase firebaseDatabase; private DatabaseReference databaseReference; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_comic); Intent intent= getIntent(); get_key = intent.getStringExtra("keys"); firebaseDatabase = FirebaseDatabase.getInstance(); databaseReference = firebaseDatabase.getReference("bookshelf/data/"+get_key+"/pages"); Log.d(TAG, "bookshelf/data/"+get_key+"/pages"); response_data = new ArrayList<>(); LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); mRecyclerView = (RecyclerView) findViewById(R.id.recycleComic); mRecyclerView.setLayoutManager(layoutManager); dataAdapter = new ComicDataAdapter(response_data); mRecyclerView.setAdapter(dataAdapter); comicBindingData(); } private void comicBindingData() { databaseReference.addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String s) { response_data.add(dataSnapshot.getValue(DataModel.class)); dataAdapter.notifyDataSetChanged(); } @Override public void onChildChanged(DataSnapshot dataSnapshot, String s) { } @Override public void onChildRemoved(DataSnapshot dataSnapshot) { } @Override public void onChildMoved(DataSnapshot dataSnapshot, String s) { } @Override public void onCancelled(DatabaseError databaseError) { } }); } }
ทดสอบแอปพลิเคชันของเราให้คลิกที่หน้าปก แล้วลองเลื่อนอ่านดู สามารถเปลี่ยนเป็น Slide แนวนอนได้นะแค่เปลี่ยน
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); mRecyclerView = (RecyclerView) findViewById(R.id.recycleComic);
เลื่อนอ่านดูจะเห็น View ต่อเนื่องกัน อ่านเพลินๆ
เสร็จแล้วครับ การสร้างแอปพลิเคชัน Android ด้วย Firebase ทำแอปพลิเคชันอ่านการ์ตูนง่ายๆ ประยุกต์ใช้ทำ Ebook ทั้งแบบ PDF และ แบบ Image List
ปล. ใครที่ขอ Source Code ขอบอกว่า ทำตามครับ ค่อยๆ อ่าน ถ้าทำตามยังทำไม่ได้ก็ไปทำอย่างอื่นดีกว่าครับ : )
One Comment