04 เมษายน 2556

[Android Code] Scroll View Slider การทำให้ Scroll View เลื่อนอัตโนมัติ


หายหัวไปซักพักนึงคราวนี้ก็กลับมาแล้ว เป็นบทความโค๊ดตัวอย่างบ้าง
โดยบทความคราวนี้ก็จะเป็นตัวอย่างการทำให้ Scroll View เลื่อนได้
แต่การที่ Scroll View เลื่อนได้ในที่นี้ไม่ได้หมายถึงผู้ใช้เลื่อนลงเองนะ
จะหมายถึงเลื่อนแบบอัตโนมัติ โดยจะให้เลื่อนลงข้างล่างเรื่อยๆจนสุด
และมี Seek Bar เพื่อให้สามารถเลื่อนเพื่อปรับความเร็วในการเลื่อนได้ด้วย


สภาพโดยรวมของตัวอย่างเจ้าของบล็อกจะไม่แต่งอะไรมาก
เพื่อไม่ให้มันเยอะเกิน เดี๋ยวผู้ที่หลงเข้ามาอ่านบางคนจะงง
ก็เอาตัวอย่างนี้ไปประยุกต์ใช้ให้ตรงกับที่ต้องการละกัน

main.xml
<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" > <ScrollView android:id="@+id/scrollView1" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/seekBarSpeed" android:layout_centerHorizontal="true" android:scrollbars="none" > <LinearLayout android:id="@+id/layoutScroll" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="20dp" android:layout_marginTop="20dp" android:text="Text 1" android:textSize="30dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="20dp" android:layout_marginTop="20dp" android:text="Text 2" android:textSize="30dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="20dp" android:layout_marginTop="20dp" android:text="Text 3" android:textSize="30dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="20dp" android:layout_marginTop="20dp" android:text="Text 4" android:textSize="30dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="20dp" android:layout_marginTop="20dp" android:text="Text 5" android:textSize="30dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="20dp" android:layout_marginTop="20dp" android:text="Text 6" android:textSize="30dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="20dp" android:layout_marginTop="20dp" android:text="Text 7" android:textSize="30dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="20dp" android:layout_marginTop="20dp" android:text="Text 8" android:textSize="30dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="20dp" android:layout_marginTop="20dp" android:text="Text 9" android:textSize="30dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="20dp" android:layout_marginTop="20dp" android:text="Text 10" android:textSize="30dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="20dp" android:layout_marginTop="20dp" android:text="Text 11" android:textSize="30dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="20dp" android:layout_marginTop="20dp" android:text="Text 12" android:textSize="30dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="20dp" android:layout_marginTop="20dp" android:text="Text 13" android:textSize="30dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="20dp" android:layout_marginTop="20dp" android:text="Text 14" android:textSize="30dp" /> </LinearLayout> </ScrollView> <Button android:id="@+id/buttonSlide" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:text="Slide" /> <SeekBar android:id="@+id/seekBarSpeed" android:layout_width="200dp" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:progress="30" android:max="60" /> </RelativeLayout>



สำหรับตัวอย่างนี้จะเห็นว่าเจ้าของบล็อกสร้าง Scroll View ขึ้นมา
โดยให้ข้างในประกอบไปด้วย Text View หลายๆ อันต่อๆ กัน
จนล้นหน้าจอ เพื่อให้เห็นภาพตัวอย่างในการนำไปใช้งานจริง
ซึ่งจะมี Text View อยู่ทั้งหมด 17 ตัว แต่จะเห็นว่าตอนแสดงบนจอ
จะแสดงได้เพียงแค่ 6 ตัวเท่านั้น (ขึ้นอยู่กับขนาดหน้าจอของผู้ใช้)
Text View อีก 11 ตัวที่เหลือจะต้องเลื่อนลงมาข้างล่างจึงจะเห็น
มี Seek Bar สำหรับปรับความเร็ว และ Button เพื่อเลื่อนอัตโนมัติ

Main.java
package app.akexorcist.scrollviewslider; import android.os.Bundle; import android.app.Activity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; public class Main extends Activity { LinearLayout layoutScroll; ScrollView scrollView1; Button buttonSlide; SeekBar seekBarSpeed; Boolean isBottom = true; int speed; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); layoutScroll = (LinearLayout)findViewById(R.id.layoutScroll); scrollView1 = (ScrollView)findViewById(R.id.scrollView1); seekBarSpeed = (SeekBar)findViewById(R.id.seekBarSpeed); seekBarSpeed.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) { speed = arg1 + 5; } public void onStartTrackingTouch(SeekBar seekBar) { } public void onStopTrackingTouch(SeekBar seekBar) { } }); speed = seekBarSpeed.getProgress(); buttonSlide = (Button)findViewById(R.id.buttonSlide); buttonSlide.setOnClickListener(new OnClickListener() { public void onClick(View v) { scolling(); } }); } public void scolling() { new Thread(new Runnable() { int bottom = layoutScroll.getHeight() - scrollView1.getHeight(); public void run() { if(isBottom) { isBottom = false; runOnUiThread(new Runnable() { public void run() { scrollView1.scrollTo(0, 0); } }); while(!isBottom) { try { runOnUiThread(new Runnable() { public void run() { scrollView1.scrollBy(0, 1); if(scrollView1.getScrollY() == bottom) isBottom = true; } }); Thread.sleep(speed); } catch (InterruptedException e) { } } } else { isBottom = true; } } }).start(); } public void onPause() { super.onPause(); isBottom = true; } }

1. ประกาศ Object ต่างๆ ที่จะเรียกใช้งาน จะเห็นว่าไม่ได้ประกาศของ Text View
เพราะไม่ได้ใช้งานอะไร ในการเลื่อนอัตโนมัติจะไปสั่งงานที่ Scroll View แทน
ดังนั้น Text View ที่อยู่ใน Scroll View ใช้แค่แสดงเฉยๆ ไม่ได้มีการควบคุมอะไร

2. ประกาศตัวแปรที่ใช้งาน โดย isBottom สำหรับเก็บสถานะว่าเลื่อนลงสุดหรือยัง
ส่วน speed คือตัวแปรสำหรับกำหนดความเร็วในการเลื่อนอัตโนมัตินั่นเอง
ซึ่งค่าของตัวแปร speed นี้จะใช้กำหนดระยะเวลาในการหน่วงเวลา (รออ่านต่อไป)

3. กำหนดค่าให้กับ Object ของคลาส Linear Layout และ Scroll View

4. กำหนดค่าให้กับ Seek Bar และประกาศเรียกใช้ Event Listener
เป็น onSeekBarChanged ซึ่งก็คือเมื่อ Seek Bar มีการเลื่อนเพื่อปรับค่า
โดย Listener ตัวนี้จะมีฟังก์ชันด้วยกัน 3 ฟังก์ชัน แต่ว่าจะใช้แค่อันเดียว
คือ onProgressChanged เป็นฟังก์ชันเมื่อค่าบน Seek Bar เปลี่ยนแปลงแล้ว
onStartTrackingTouch กับ onStopTracking Touch ไม่ใช้ แต่ต้องประกาศไว้

5. ในฟังก์ชัน onProgressChanged จะกำหนดให้ตัวแปร speed กำหนดค่าจาก
Seek Bar บวกไปอีก 5 ถ้าดูจาก main.xml จะเห็นว่ากำหนดไว้สูงสุดที่ 60
ซึ่งค่าที่ว่านี้ก็คือค่าหน่วงเวลาในการเลื่อน Scroll View ถ้าหน่วงมากก็เลื่อนช้า
ดังนั้นค่าต่ำสุดก็คือหน่วงเวลา 5 มิลลิวินาที และสูงสุด 65 มิลลิวินาทีนั่นเอง

6. กำหนดค่าให้ Button สำหรับกดเพื่อเริ่มเลื่อนอัตโนมัติ และ Event Listener
โดยให้กดแล้วเรียกฟังก์ชัน scrolling ซึ่งเป็นฟังก์ชันควบคุมการเลื่อนอัตโนมัติ

7. ฟังก์ชันสำหรับควบคุมการเลื่อนอัตโนมัติของ Scroll View
โดยจะใช้วิธีสร้าง Thread แยกขึ้นมาเพื่อสั่งให้ Scroll View เลื่อน
ซึ่งจะมีการคำนวณหาว่าระยะล่างสุดของ Scroll View มีค่าเท่าไร
เพราะการใช้ค่าจากคำสั่ง scrollView1.getBottom() นั้น ได้ค่าไม่ล่างสุด
ถ้านำค่าดังกล่าวไปใช้ เวลาเลื่อนอัตโนมัติ จะเลื่อนได้ไม่สุดข้างล่าง
จึงใช้วิธีคำนวณหาจากการเอาความสูงของ Layout ที่อยู่ใน Scroll View
ลบออกด้วยความสูงของ Scroll View จะได้ระยะล่างสุดของ Scroll View

8. เมื่อคำนวณหาค่าระยะล่างสุดแล้วก็จะเช็คค่าจากตัวแปร isBottom
ถ้าเป็น True ก็หมายความว่าเลื่อนอัตโนมัติเป็นครั้งแรกที่เปิดแอปฯ ขึ้น
(เพราะตอนที่แอปฯ เริ่มทำงานได้กำหนดให้ตัวแปรดังกล่าวเป็น True)
หรือเลื่อนมาข้างล่างสุดของ Scroll View แล้ว ก็จะให้ตัวแปรเป็น False
แล้วกำหนดให้ Scroll View กลับมาอยู่ที่ตำแหน่งบนสุด (0 , 0) ก่อน
แล้วเข้าสู่ While เพื่อวนลูปสำหรับเลื่อน Scroll View อัตโนมัติ
จะเห็นว่ามีการใช้ runOnUiThread ด้วย ทั้งนี้เพราะว่าคำสั่ง scrollTo
เป็นคำสั่งที่สามารถใช้ได้ใน Thread หลักเท่านั้น ซึ่งฟังก์ชัน scrolling
ได้สร้าง Thread แยกขึ้นมา ถ้าคำสั่งดังกล่าวทำงานใน Thread แยก
ก็จะเกิด Exception หรือเออเรอร์ขึ้น ดังนั้นจึงให้ทำงานใน Thread หลัก
เฉพาะแค่คำสั่งดังกล่าวเท่านั้น ส่วนคำสั่งอื่นๆที่ไม่จำเป็นก็ไม่ต้อง

9. ถ้าตัวแปร isBottom เป็น False ก็จะกำหนดให้ตัวแปรเป็น True แทน
ซึ่งในการกำหนดให้เป็น True จะมีความสำคัญอยู่อย่างหนึ่งคือ
ในกรณีที่เลื่อนอัตโนมัติอยู่ ตัวแปร isBottom ก็จะต้องเป็น False
เมื่อกด buttonSlide อีกครั้งก็เข้าเงื่อนไขนี้ เพื่อกำหนดค่าให้เป็น True
แต่ในการกด buttonSlide เพื่อให้เลื่อนอัตโนมัติ ให้สังเกตุที่คำสั่งใน
เงื่อนไข isBottom เป็น True ดูที่ While ที่เจ้าของบล็อกกำหนดให้
วนลูปไปเรื่อยๆเมื่อตัวแปร isBottom เป็น False แต่เมื่อกลายเป็น True
ผลก็คือในขณะที่กำลังเลื่อน Scroll View อัตโนมัติอยู่ ก็จะหยุดทันที
ซึ่งเงื่อนไขใน While ไม่เป็นจริงแล้ว เพราะ isBottom กลายเป็น True

10. เงื่อนไขที่เกิดจากการที่ isBottom กลายเป็น False หลังจากที่
กด buttonSlide เพื่อเริ่มให้เลื่อนอัตโนมัติ ก็จะให้เลื่อนลงไป 1 px
และมีการเช็คว่าได้เลื่อน Scroll View ไปถึงข้างล่างสุดหรือยัง
โดยเทียบจากตัวแปร bottom ที่ได้คำนวณไว้ในตอนแรก (เลข 7)

11. ฟังก์ชัน onPause เอาไว้ป้องกันเวลาที่เลื่อนอัตโนมัติอยู่
แล้วผู้ใช้กดย่อหรือปิดแอปฯ ก็จะให้ตัวแปร isBottom เป็น True
Thread ย่อยที่ควบคุมให้ Scroll View เลื่อน ก็จะออกจากลูป While
เพราะถ้าทำแบบนี้ จะทำให้ Thread ย่อยที่วนลูป While อยู่
จะยังคงทำงานอยู่ถึงแม้ว่าผู้ใช้จะย่อหรือปิดแอปฯ แล้วก็ตาม


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.akexorcist.scrollviewslider" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="app.akexorcist.scrollviewslider.Main" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>


สำหรับผู้ที่หลงเข้ามาอ่านผู้ใดที่ต้องการดาวน์โหลดไฟล์ตัวอย่าง
สามารถดาวน์โหลดได้จาก Scroll View Slider [Google Drive]




เหล่าพันธมิตรแอนดรอยด์

Devahoy Layer Net NuuNeoI The Cheese Factory Somkiat CC Mart Routine Artit-K Arnondora Kamonway Try to be android developer Oatrice Benz Nest Studios Kotchaphan@Medium Jirawatee@Medium Travispea@Medium