Day 18 : ShowcaseView

Day 18 : ShowcaseView Cover Image

สวัสดีครับ บทความนี้เป็นบทความที่ 18 แล้วนะครับ ที่ผมจะมาเขียน ในซีรีย์ Learn 30 Android Libraries in 30 days

สำหรับบทความทั้งหมด อ่านได้จากด้านล่างครับ

สำหรับวันนี้ขอนำเสนอเรื่อง ShowcaseView เป็น Library ที่เอาไว้ทำเป็น Guide หรือ Showcase ให้แอพของเรา โดยโฟกัสไปยังตำแหน่งที่เราต้องการครับ

ShowcaseView

Installation

ขั้นตอนการติดตั้ง เพิ่ม dependencies ลงไปที่ไฟล์ build.gradle (ไฟล์ภายใน Module)

dependencies {
    compile ('com.fortysevendeg.swipelistview:swipelistview:[email protected]') {
        transitive = true
    }
}

ต่อจากนั้นก็เพิ่ม Maven Central ที่ไฟล์ build.gradle ใน Root Project ดังนี้

repositories {
     mavenCentral()
}

ต่อมาทำการดาวน์โหลดตัว Library มาไว้ในโปรเจ็คของเรา จากนั้นก็ เปิดไฟล์ settings.gradle แล้วทำการ include Library ที่โหลดมาเมื่อกี้ให้ถูกต้อง

include ':libs:showcaseview:library'

ด้านบน ผมเซฟตัว Library ไว้ในโฟลเดอร์ libs ชื่อโปรเจ็คว่า showcaseview และตัวโมดูลชื่อว่า library

ShowcaseView

ทำกด Sync Project with Gradle File เป็นอันเรียบร้อย

Getting Started

การใช้งาน ShowcaseView จะมีโค๊ดดังนี้

new ShowcaseView.Builder(this)
    .setTarget(new ActionViewTarget(this, ActionViewTarget.Type.HOME))
    .setContentTitle("ShowcaseView")
    .setContentText("This is highlighting the Home button")
    .setOnClickListener(this)
    .build();

ลักษณะการสร้าง ShowcaseView จะคล้ายๆกับการทำ AlertDialog เพราะใช้ Builder Pattern เหมือนกัน เรามาดูว่าจากโค๊ดด้านบน คืออะไรบ้าง

  • setTarget() : ตัวนี้เป็นการระบุว่า จะให้มันโฟกัสไปยังตำแหน่งที่เราต้องการ
  • setContentTitle() : ใส่ Title ให้กับ ShowcaseView
  • setContentText() : ใส่ข้อความรายละเอียดให้กับ showcaseView
  • setOnClickListener() : เอาไว้รับ Listener เมื่อมีการกดปุ่ม Button

Create Project

มาลองสร้างโปรเจ็คกันเลยดีกว่าครับ จะได้เห็นภาพว่ามันใช้งานยังไง? เริ่่มแรกทำการสร้างไฟล์เลเอาท์ขึ้นมา 1 ไฟล์ ชื่อว่า activity_showcaseview.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:orientation="vertical" android:layout_width="match_parent"
                android:layout_height="match_parent">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:scaleType="centerCrop"
        android:src="@drawable/guitar" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#8831415a"
        android:id="@+id/relative_layout"
        android:padding="16dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="24sp"
            android:textColor="@color/white"
            android:text="@string/step1"
            android:id="@+id/step1"
            android:layout_marginTop="32dp"
            android:layout_alignParentTop="true"
            android:layout_alignParentLeft="true" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="24sp"
            android:textColor="@color/white"
            android:text="@string/step2"
            android:id="@+id/step2"
            android:layout_alignParentBottom="false"
            android:layout_alignParentRight="false"
            android:layout_centerInParent="true" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:textColor="@color/white"
            android:text="@string/step3"
            android:id="@+id/step3"
            android:layout_alignLeft="@+id/step1"
            android:layout_marginBottom="36dp"
            android:singleLine="false"
            android:layout_alignParentBottom="true" />
    </RelativeLayout>

</RelativeLayout>

ไฟล์ res/values/strings.xml ที่ใช้กับเลเอาท์ด้านบน

<resources>
    <string name="step1">Step 1 : Learn Android</string>
    <string name="step2">Step 2 : Make First App</string>
    <string name="step3">Step 3 : Practice, Practice and Practice</string>
    <string name="help">Help</string>
</resources>

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

เมื่อออกแบบเลเอาท์เสร็จแล้ว จะได้ลักษณะแบบนี้

Layout

ต่อมาสร้างไฟล์ ShowcaseViewActivity.java ขึ้นมา จากนั้นก็ทำการเชื่อม View จาก Layout แบบปกติครับ ดังนี้

public class ShowcaseViewActivity extends ActionBarActivity {

    private TextView mStep1, mStep2, mStep3;
    private ShowcaseView mShowcaseView;
    private int mState = 0;

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

        mStep1 = (TextView) findViewById(R.id.step1);
        mStep2 = (TextView) findViewById(R.id.step2);
        mStep3 = (TextView) findViewById(R.id.step3);
    }
}

ต่อมาทำการสร้างออปเจ็ค ShowcaseView ขึ้นมาแบบนี้

mShowcaseView = new ShowcaseView.Builder(this)
        .setTarget(new ViewTarget(mStep1))
        .build();
mShowcaseView.setButtonText("Next Step");

จะเห็นว่าด้านบน ผมทำการเซตค่า Button ให้ชื่อว่า Next Step และทำการ setTarget() ไปที่ step1 นั่นก็คือ TextView ตัวบนสุดครับ

ส่วนวิธีการสร้าง Target ทำได้ 2 แบบครับ คือ

  • new ViewTarget(view); : โดย view ต้องเป็น view ที่เราทำการ findViewById() แล้ว
  • new ViewTarget(id, Activity) : parameter ตัวแรก คือ id ของ View เช่น R.id.step1 และตัวสองคือ Activity

ลองทดสอบรันดูครับ ผลลัพธ์จะได้ดังภาพ

Step1

จะเห็นว่ามันมีวงกลมไปโฟกัส ที่ตำแหน่ง TextView ที่ชื่อ mStep1 ที่เรา setTarget ไว้ และก็มี Button ปุ่มขวาล่างที่เราตั้งชื่อว่า Next Step ทีนี้ลองเปลี่ยนจาก mStep1 เป็น mStep2 ดูครับ

.setTarget(new ViewTarget(mStep2))

ลองรันใหม่อีกครั้ง

Step2

แต่ว่าเมื่อเรากดปุ่ม Button ตัว ShowcaseView มันก็จะหายไปทันที ทีนี้เราจะทำยังไง เมื่อกด ก็ให้มันไล่ไปทีละสเตปๆ เริ่มจาก mStep1 ไปจนถึง mStep3 ครับ

ตัว ShowcaseView มี Listener ให้เหมือนกันครับ เอาไว้รับ listener เหมือน Button ปกติเลยครับ กลับไปแก้ไขตรง ShowcaseView.Builder() ให้ครับ โดยเพิ่ม setOnClickListener(ShowcaseViewOnClick) ไปก่อนที่จะทำการ build() เป็นดังนี้

mShowcaseView = new ShowcaseView.Builder(this)
        .setTarget(new ViewTarget(mStep1))
        .setOnClickListener(ShowcaseViewOnClick)
        .build();
mShowcaseView.setButtonText("Next Step");

ทีนี้ก็สร้าง interface ShowcaseViewOnClick ขึ้นมา (จริงๆ ใครถนัดทำ anonymous inner class ก็ได้นะครับ หรือจะ implements interface แล้ว override เมธอด onClick เอาก็ได้เหมือนกัน แต่ผมชอบวิธีนี้มากกว่า)

เรื่องที่เกี่ยวข้อง : Android Button และการรับ Event ด้วย OnClick

View.OnClickListener ShowcaseViewOnClick = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        switch (mState) {
            case 0:
                mShowcaseView.setShowcase(new ViewTarget(mStep2), true);
                // mShowcaseView.setTarget(new ViewTarget(mStep2));
                break;
            case 1:
                mShowcaseView.setShowcase(new ViewTarget(mStep3), true);
                // mShowcaseView.setTarget(new ViewTarget(mStep3));
                break;
            case 2:
                ActionViewTarget target = new ActionViewTarget(ShowcaseViewActivity.this,
                        ActionViewTarget.Type.OVERFLOW);
                mShowcaseView.setTarget(target);
                break;
            default:
                mShowcaseView.hide();
                mLayout.setBackgroundColor(Color.TRANSPARENT);
                setVisible(true);
                break;
        }
        mState += 1;
    }
};

ข้อควรระวัง import OnClickListener ให้ถูก package ด้วยนะครับ ต้องเป็น

import android.view.View.OnClickListener;

จะเห็นว่าใน onClick() ผมทำการเพิ่มค่า mState ทุกครั้งที่มีการคลิก Button จากนั้นก็ทำ switch case เช็คว่า ถ้ากดครั้งแรก ให้ไปที่ step2 กดครั้งที่สองให้ไป Step3 กดครั้งที่สามไปบน ActionBar และกดครั้งสุดท้าย จะเป็นการซ่อน ShowcaseView

  • setShowcase(Target, animation); : จะโฟกัสและทำการเล่น Animation ด้วย หากค่าเป็น true
  • setTarget(Target); : จะแค่โฟกัสอย่างเดียว ไม่มีการเล่น animation

สุดท้ายผมทำการแก้โค๊ดใหม่ แล้วลองทดสอบอีกครั้งครับ โดยเพิ่มในส่วน menu เข้าไปด้วย res/menu/menu.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:id="@+id/menu_item"
          android:title="@string/help"
          app:showAsAction="never"/>
</menu>

สุดท้ายเพิ่ม onCreateOptionsMenu ลงไป และโค๊ดทั้งหมดเป็นแบบนี้

package com.devahoy.learn30androidlibraries.day18;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.devahoy.learn30androidlibraries.R;
import com.github.amlcurran.showcaseview.ShowcaseView;
import com.github.amlcurran.showcaseview.targets.ActionViewTarget;
import com.github.amlcurran.showcaseview.targets.Target;
import com.github.amlcurran.showcaseview.targets.ViewTarget;

public class ShowcaseViewActivity extends ActionBarActivity {

    private TextView mStep1, mStep2, mStep3;
    private ShowcaseView mShowcaseView;
    private RelativeLayout mLayout;
    private int mState = 0;

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

        mStep1 = (TextView) findViewById(R.id.step1);
        mStep2 = (TextView) findViewById(R.id.step2);
        mStep3 = (TextView) findViewById(R.id.step3);
        mLayout = (RelativeLayout) findViewById(R.id.relative_layout);
    }

    View.OnClickListener ShowcaseViewOnClick = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (mState) {
                case 0:
                    mShowcaseView.setShowcase(new ViewTarget(mStep2), true);
//                    mShowcaseView.setTarget(new ViewTarget(mStep2));

                    break;
                case 1:
                    mShowcaseView.setShowcase(new ViewTarget(mStep3), true);
//                    mShowcaseView.setTarget(new ViewTarget(mStep3));
                    break;
                case 2:
                    ActionViewTarget target = new ActionViewTarget(ShowcaseViewActivity.this,
                            ActionViewTarget.Type.OVERFLOW);
//                    mShowcaseView.setTarget(target);
                    mShowcaseView.setTarget(target);
                    mLayout.setBackgroundColor(Color.parseColor("#FF31415A"));
                    mShowcaseView.setContentTitle("This is Help Menu");
                    setVisible(false);
                    mShowcaseView.setButtonText("OK");
                    break;
                default:
                    mShowcaseView.hide();
                    mLayout.setBackgroundColor(Color.TRANSPARENT);
                    setVisible(true);
                    break;
            }
            mState += 1;
        }
    };

    public void setVisible(boolean isVisible) {
        if(isVisible) {
            mStep1.setVisibility(View.VISIBLE);
            mStep2.setVisibility(View.VISIBLE);
            mStep3.setVisibility(View.VISIBLE);
        } else {
            mStep1.setVisibility(View.GONE);
            mStep2.setVisibility(View.GONE);
            mStep3.setVisibility(View.GONE);
        }
    }

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

        mShowcaseView = new ShowcaseView.Builder(this)
                .setTarget(new ViewTarget(mStep1))
                .setOnClickListener(ShowcaseViewOnClick)
                .build();

        mShowcaseView.setButtonText("Next Step");

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        return true;
    }
}

ได้ผลลัพธ์เป็นที่น่าพอใจครับ กด Button ก็จะเลื่อนสเตปไปเรื่อยๆ

สรุป

ตัว ShowcaseView ผมว่าเหมาะเอาไปทำพวก Tutorial หรือแนะนำการใช้แอพสำหรับผู้ใช้งานใหม่ หรือแนะนำเมนูว่ามีเมนูอะไรบ้าง ตำแหน่งนี้ทำอะไร น่าจะเหมาะอยู่ครับ และการใช้งานก็ไม่ยุ่งยากด้วย ลองนำไปประยุกต์และใช้กันดูนะครับ Happy Coding :D

Source Code

References

Chai Chai Phonbopit : MEAN Stack @Nextzy • ผู้ชายธรรมดาๆ ที่ชื่นชอบ Android, JavaScript (Node.js) และ Open Source มีงานอดิเรกเป็น Acoustic Guitar และ Football

บทความล่าสุด