05 December 2014

Let's Fragment - เพิ่มลูกเล่นให้กับ View Pager ด้วย Page Transformer

Updated on


        หายหัวไปหลายวัน วันนี้กลับมาต่อกับเรื่อง View Pager เหมือนเดิม ซึ่งบทความที่แล้วเป็นการสร้าง View Pager (แสนยืดยาว) คราวนี้มาลองทำให้ View Pager มีลูกเล่นเล็กๆน้อยๆระหว่างเปลี่ยนหน้ากันดีกว่า


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


        ซึ่งเจ้า Transition ที่ว่านี้จะเรียกว่า Page Transformer


สร้าง Page Transformer Class เพื่อใช้กับ View Pager

        เจ้าของบล็อกจะสร้างคลาสขึ้นมาโดยตั้งชื่อว่า MyPageTransformer

MyPageTransformer.java
public class MyPageTransformer {

}

        เนื่องจาก PageTransformer เป็น Interface Class เหมือนกับ Listener ดังนั้นจะต้อง Implement แทนการ Extends โดยที่จะมี Implement Method เป็น transformPage เพียงเมธอดเดียว

MyPageTransformer.java
import android.support.v4.view.ViewPager;
import android.view.View;

public class MyPageTransformer implements ViewPager.PageTransformer {

    public void transformPage(View view, float position) {

    }

}

        ทีนี้ก็ให้สังเกตที่ transformPage ซึ่งจะมี Parameter อยู่สองตัวคือ View และ Position โดยที่

        • View : View ของ Fragment ตัวที่แแสดง (จากการทดสอบ View นี้ไม่ใช่ View ที่ Return ใน onCreateView น่าจะเป็น Parent อีกที)

        • Position : ตำแหน่งของ Fragment เมื่อมีการเลื่อน โดยมีค่าเป็น Float ไม่มีหน่วย เพราะเป็นอัตราส่วนเทียบกับหน้าจอของอุปกรณ์แต่ละเครื่อง โดยที่ตำแหน่ง 0 คือตำแหน่งที่ Fragment แสดงที่หน้าจอพอดี ส่วน -1 คือ Fragment เลื่อนไปสุดทางซ้ายมือของหน้าจอ และ 1 คือเลื่อนไปสุดทางขวาของหน้าจอ






        แต่ความยากจะอยู่ที่ว่า transformPage จะทำงานพร้อมๆสำหรับ Fragment 2 ตัว




        เมื่อเกิด Event ขึ้นแต่ละครั้ง จะทำให้ transformPage ถูกเรียกสองครั้งสำหรับ Fragment แต่ละตัว จึงอาจจะทำให้งงได้ถ้าเขียนคำสั่งไม่ดีพอ

        เนื่องจากมี Parameter เป็น View ด้วย จึงสามารถแยกแยะได้ว่าเป็น Event ของ View ตัวไหน

        แต่เอาเข้าจริงมันก็ไม่ได้ง่ายขนาดนั้นนะ!

        เพราะ Fragment ใน View Pager ไม่ได้มีแค่ 2 ตัวอยู่แล้ว อาจจะมากถึง 5 ตัว ดังนั้นการแยกแยะจาก View จะไม่ช่วยอะไรเลย เพราะมีการสลับไปมาตลอด

        ดังนั้นทางที่ถูกต้องก็คือทำให้ Transition สามารถทำงานได้ไม่ว่าจะเป็น Fragment ฝั่งซ้ายหรือขวา เพราะหัวใจสำคัญนั้นอยู่ที่ว่า Position ของ Page Transform จะมีค่าอยู่ในช่วง -1 ถึง 1 (นอกเหนือจากนี้ไม่สนใจ)

        ถ้าต้องการให้ Transition มีผลลัพธ์ตรงข้ามกันก็ใช้ 0 เป็นตัวแบ่งได้ และถ้าอยากให้ทำงานเหมือนๆกันก็แค่ใช้ Math.abs เพื่อทำให้ -1 กลายเป็น 1 เหมือนกับอีกฝั่งนั่นเอง
     

        เอ้า! งงล่ะสิ!? ถ้าอย่างงั้นก็มาดูตัวอย่างกันดีกว่า~

        ยกตัวอย่างว่าเจ้าของบล็อกเขียนให้ Page Transformer สามารถโปร่งใสขึ้นเรื่อยๆเมื่อเลื่อนออกนอกจากหน้าจอ ดังนั้นที่ View จะมี Alpha เป็น 100% ที่ Position เป็น 0 และมี Alpha เป็น 0% ที่ Position เป็น 1 หรือ -1

        เนื่องจาก 1 และ -1 ต้องการให้ผลลัพธ์เหมือนกันคือ Alpha เป็น 0% ดังนั้นจึงลดความยุ่งยากด้วยการทำให้ทั้งสองค่ามีค่าเท่ากัน นั่นก็คือจับยัดเข้าไปใน Math.abs ซะ เท่านี้ค่าทั้งสองก็จะเท่ากันแล้ว ไม่ว่าจะ -1 กับ 1 หรือว่า -0.6 กับ 0.6 เพราะ Math.abs จะทำให้ค่าที่ติดลบนั้นกลายเป็นบวก

float val = (float)(Math.abs(position));

        เนื่องจาก Math.abs จะ Return กลับมาเป็น Double จึงแปลงเป็น Float แทน

        แต่ทว่าแค่นี้ก็ยังไม่จบซะหน่อย เพราะว่าค่า position ก็จะถูกแปลงมาให้อยู่ในช่วง 0 ถึง 1 ก็เท่านั้น

        ถ้าเอาค่าดังกล่าวนี้ไปแทนโดยตรงจะเกิดปัญหาว่า View มีค่า Alpha เป็น 0 ที่ Position 0 และ Alpha จะเป็น 100%  ที่ Position 1 และ -1 ซึ่งมัน Invert กันนี่นาาาาา

        เจ้าของบล็อกก็แค่เปลี่ยนวิธีคำนวณให้ค่าออกมาเป็น 0 ถึง 1 ตามที่ต้องการซะ

float val = 1 - (float)(Math.abs(position));

        ซึ่งค่าที่ได้นี้ก็คือค่า Alpha นั่นเอง ดังนั้นก็เอาค่าดังกล่าวมากำหนด Alpha ให้กับ View ได้เลย

MyPageTransformer.java
import android.support.v4.view.ViewPager;
import android.view.View;

public class MyPageTransformer implements ViewPager.PageTransformer {

    public void transformPage(View view, float position) {
        float alpha = 1 - (float)(Math.abs(position));
        view.setAlpha(alpha);
    }

}

        แล้วกรณีที่ Position มากกว่า 1 ขึ้นไป หรือน้อยกว่า -1 ลงไปล่ะ?

        View มันเลยหน้าจอไปแล้วก็ให้ Alpha เป็น 0 สิ!

MyPageTransformer.java
import android.support.v4.view.ViewPager;
import android.view.View;

public class MyPageTransformer implements ViewPager.PageTransformer {

    public void transformPage(View view, float position) {
        if(position > 1 || position < -1) {
            view.setAlpha(0);
        } else {
            float alpha = 1 - (float)(Math.abs(position));
            view.setAlpha(alpha);
        }
    }

}
     
        สร้าง Page Transformer เสร็จ แล้วยังไงต่อล่ะ?

        วิธีการนำไปใช้นั้นแสนง่าย เพราะ View Pager จะมีคำสั่งกำหนด Page Transformer อยู่แล้ว

pager.setPageTransformer(true, new MyPageTransformer());

        สำหรับ Boolean ที่กำหนดไว้ว่า True นั้นไม่ต้องสนใจอะไรมากนัก มันคือการกำหนดว่าจะให้ MyPageTransformter ทำงานที่ View ตัวท้ายก่อนจนถึงตัวแรก หรือว่าเริ่มทำงานที่ View ตัวแรกก่อนจนถึงตัวท้าย ซึ่งไม่มีผลต่อโค๊ดหลักซักเท่าไรนัก

        ส่วน Page Transformer ก็จะสร้าง Instance ของ MyPageTransformer เพื่อกำหนดในนี้แทน ซึ่งถ้ามองดีๆมันก็คล้ายๆกับ Listener เลย ปกติเจ้าของบล็อกจะประกาศแบบ Anonymous แต่ว่าในกรณีนี้จะไปสร้างคลาสตัวใหม่แล้ว Implement Page Transformer ในนั้นแทน


        จะเขียนสั้นๆแบบนี้ก็ได้

pager.setPageTransformer(true, new ViewPager.PageTransformer() {
    public void transformPage(View view, float position) {
        if (position > 1 || position < -1) {
            view.setAlpha(0);
        } else {
            float alpha = 1 - (float) (Math.abs(position));
            view.setAlpha(alpha);
        }
    }
});


        สำหรับโค๊ดที่ใช้ก็จะเพิ่มเติมต่อจากบทความที่แล้วเพียงแค่เพิ่ม Page Transformer เข้าไป

MainActivity.java
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager;

public class MainActivity extends FragmentActivity {
    MyPageAdapter adapter;
    ViewPager pager;
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        getActionBar().hide();
        adapter = new MyPageAdapter(getSupportFragmentManager());
        
        pager = (ViewPager) findViewById(R.id.pager);
        pager.setAdapter(adapter);
        
        pager.setPageTransformer(true, new MyPageTransformer());
    }
}

MyPageTransformer.java
pager.setPageTransformer(true, new ViewPager.PageTransformer() {
    public void transformPage(View view, float position) {
        if (position > 1 || position < -1) {
            view.setAlpha(0);
        } else {
            float alpha = 1 - (float) (Math.abs(position));
            view.setAlpha(alpha);
        }
    }
});


        โค๊ดอื่นๆไม่ขอแปะนะ เพราะเหมือนเดิมเลย บทความจะยืดยาวเสียเปล่า

        การประยุกต์ใช้ Page Transformer เมื่อเกิด Transition จากการ Swipe เปลี่ยนหน้านั้นมีอยู่หลากหลายวิธี ซึ่งสามารถดูตัวอย่างง่ายๆได้จาก Using ViewPager for Screen Slides [Android Developer]


        ดังนั้นอยากให้ Transition เป็นแบบไหนก็อยู่ที่ผู้ที่หลงเข้ามาอ่านแล้วล่ะว่าจะเขียนควบคุมมันอย่างไร~

        ไม่แจกโค๊ดจ้า อยากให้ลองทำเองเหมือนเคย