Android 引導頁實現

背景

如果我們的 APP 中需要加載一些資源,或者需要一個過渡的界面讓主界面顯示的不是那麼的突兀,這時候我們就需要使用到引導頁這麼一個概念了,通過在引導頁的期間去加載一些資源,讓用戶不至於乾等,導致給予用戶一個不好的體驗。
Android 中引導頁的實現主要有四種辦法:

  • Splash 界面
  • ViewPager
  • ViewFlipper
  • ScrollView

接下來就依次去把他進行實現。

Splash 界面

這個方法是最簡單的一種方法,其實就是對主界面進行一個延遲的 Start,這一實現利用 Handler 就可以完成了。

package com.xjh.bootpagedemo.splash

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import com.xjh.bootpagedemo.MainActivity
import com.xjh.bootpagedemo.R

class SplashActivity : AppCompatActivity() {

    companion object {
        private val DELAY_TIME = 3000L
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_splash)
        Handler().postDelayed({
            startActivity(Intent(this, MainActivity::class.java))
            finish()
        }, DELAY_TIME)
    }
}

ViewFlipper

假如說我們想要又翻頁的效果,這樣可以用於 APP 的產品介紹,這樣就要結合 ViewFlipper 來進行實現了,這個是 Android 自帶的一個多界面展示 View。我們直接在 XML 中進行定義就好了。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ViewFlipper
        android:id="@+id/viewFlipper"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@color/colorAccent" />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@color/colorPrimary" />

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/colorPrimaryDark">

            <Button
                android:id="@+id/btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:layout_centerHorizontal="true"
                android:layout_marginBottom="60dp"
                android:gravity="center"
                android:text="進入主頁"
                android:textSize="22sp" />

        </RelativeLayout>

    </ViewFlipper>

    <LinearLayout
        android:id="@+id/indicator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal|bottom"
        android:layout_marginBottom="30dp"
        android:orientation="horizontal" />

</FrameLayout>

在 Activity 中也很簡單,我們只需要去監聽手勢去判斷是向哪個方向滑動的,然後去調用相關的方法就能進行實現,並且在 ViewFlipper 進行變化的時候改變對應的指示器就可以了。

package com.xjh.bootpagedemo.viewflipper

import android.app.ActionBar
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.TypedValue
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import androidx.core.view.get
import com.xjh.bootpagedemo.MainActivity
import com.xjh.bootpagedemo.R
import kotlinx.android.synthetic.main.activity_view_flipper.*
import kotlinx.android.synthetic.main.activity_view_pager.*
import kotlinx.android.synthetic.main.activity_view_pager.indicator
import kotlinx.android.synthetic.main.fragment_content.view.*

class ViewFlipperActivity : AppCompatActivity(), GestureDetector.OnGestureListener {

    private lateinit var gestureDetector: GestureDetector
    private var index = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_flipper)
        btn.setOnClickListener {
            startActivity(Intent(this, MainActivity::class.java))
        }
        initIndicator()
    }

    private fun initIndicator() {
        val width =
            TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,
                20f,
                resources.displayMetrics
            ).toInt()
        val lp = ActionBar.LayoutParams(width, width)
        lp.rightMargin = 2 * width
        lp.leftMargin = 2 * width
        for (i in 0 until viewFlipper.childCount) {
            val view = View(this)
            view.id = i
            view.setBackgroundResource(if (i == 0) R.drawable.dot_focus else R.drawable.dot_normal)
            view.layoutParams = lp
            indicator.addView(view, i)
        }

        gestureDetector = GestureDetector(this)
    }

    override fun onShowPress(e: MotionEvent?) {

    }

    override fun onSingleTapUp(e: MotionEvent?): Boolean {
        return false
    }

    override fun onDown(e: MotionEvent?): Boolean {
        return false
    }

    override fun onFling(
        e1: MotionEvent?,
        e2: MotionEvent?,
        velocityX: Float,
        velocityY: Float
    ): Boolean {
        e1?.let { a ->
            e2?.let { b ->
                if (a.x > b.x) {
                    viewFlipper.showNext()
                    index = if (index < 2) index + 1 else 0
                    changeIndicator()
                    return true
                } else if (a.x < b.x) {
                    viewFlipper.showNext()
                    index = if (index > 0) index - 1 else 2
                    changeIndicator()
                    return true
                }
            }
        }
        return false
    }

    private fun changeIndicator() {
        for (i in 0 until viewFlipper.childCount) {
            indicator[i].setBackgroundResource(
                if (i == index) R.drawable.dot_focus else R.drawable.dot_normal
            )
        }
    }

    override fun onScroll(
        e1: MotionEvent?,
        e2: MotionEvent?,
        distanceX: Float,
        distanceY: Float
    ): Boolean {
        return false
    }

    override fun onLongPress(e: MotionEvent?) {

    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        return gestureDetector.onTouchEvent(event)
    }
}

當前這裏我寫的只要是有偏移就進行變化,在真正的實現中我們可以給予他一定的閾值,只有達到了那個閾值才進行頁面的變化。

ViewPager

ViewFlipper 進行實現的引導頁實現翻頁有一些突兀,因爲是直接跳過去的,沒有一個漸變的效果,想要漸變的效果就要用 ViewPager 去實現了。我們只要根據當前頁去展現不同的 Fragment,然後監聽翻頁去改變指示標的狀態就行了。
activity_view_pager.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <LinearLayout
        android:id="@+id/indicator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal|bottom"
        android:layout_marginBottom="30dp"
        android:orientation="horizontal" />

</FrameLayout>

這裏就用特別簡單的不同顏色來區分處於哪一個 Fragment。
fragment_content.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/contentRLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="60dp"
        android:gravity="center"
        android:text="進入主頁"
        android:textSize="22sp" />

</RelativeLayout>

然後就實現 Fragment,在裏面通過傳遞的參數去判斷加載哪種顏色,並且判斷是否爲最後一張,如果是最後一張就要將按鈕置爲顯示的狀態。
ContentFragment.kt

package com.xjh.bootpagedemo.viewpager

import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.xjh.bootpagedemo.MainActivity
import com.xjh.bootpagedemo.R
import kotlinx.android.synthetic.main.fragment_content.view.*

class ContentFragment : Fragment() {

    private val bgRes = arrayOf(R.color.colorAccent, R.color.colorPrimary, R.color.colorPrimaryDark)

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_content, null)
        arguments?.getInt("index")?.let {
            view.contentRLayout.setBackgroundResource(bgRes[it])
            view.btn.setOnClickListener {
                startActivity(Intent(activity, MainActivity::class.java))
            }
            view.btn.visibility = if (it == 2) View.VISIBLE else View.GONE
        }
        return view
    }
}

緊接着就要去實現 View Pager 的 Adapter 了,其實也很簡單,只要繼承於 FragmentPagerAdapter ,然後傳遞進來相關的數據列表就可以了,對應的返回就是列表中的某一項就可以了。
ViewPagerAdapter.kt

package com.xjh.bootpagedemo.viewpager

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter

class ViewPagerAdapter(fm: FragmentManager, private var fragments: List<Fragment>) :
    FragmentPagerAdapter(fm) {

    override fun getCount(): Int {
        return fragments.size
    }

    override fun getItem(position: Int): Fragment {
        return fragments[position]
    }
}

最後也是最關鍵的 Activity 就是將對應需要的 Fragment 都給實現出來,並且由此實現相關的 Adapter,給 ViewPager 加載進去。最後就是要求初始化相關的導航欄,並且監聽 ViewPager 的變化。
ViewPagerActivity.kt

package com.xjh.bootpagedemo.viewpager

import android.app.ActionBar
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.TypedValue
import android.view.View
import androidx.core.view.get
import androidx.core.view.marginLeft
import androidx.core.view.marginRight
import androidx.core.view.marginTop
import androidx.fragment.app.Fragment
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import com.xjh.bootpagedemo.R
import kotlinx.android.synthetic.main.activity_view_pager.*

class ViewPagerActivity : AppCompatActivity() {

    private lateinit var adapter: PagerAdapter
    private val fragments = ArrayList<Fragment>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_pager)

        for (index in 0..2) {
            val fragment = ContentFragment()
            val bundle = Bundle()
            bundle.putInt("index", index)
            fragment.arguments = bundle
            fragments.add(fragment)
        }

        adapter = ViewPagerAdapter(supportFragmentManager, fragments)
        viewPager.adapter = adapter
        initIndicator()
        viewPager.setOnPageChangeListener(object : ViewPager.OnPageChangeListener {
            override fun onPageScrollStateChanged(state: Int) {

            }

            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) {
                for (i in 0 until fragments.size) {
                    indicator[i].setBackgroundResource(
                        if (i == position) R.drawable.dot_focus else R.drawable.dot_normal
                    )
                }
            }

            override fun onPageSelected(position: Int) {

            }

        })
    }

    private fun initIndicator() {
        val width =
            TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,
                20f,
                resources.displayMetrics
            ).toInt()
        val lp = ActionBar.LayoutParams(width, width)
        lp.rightMargin = 2 * width
        lp.leftMargin = 2 * width
        for (i in 0 until fragments.size) {
            val view = View(this)
            view.id = i
            view.setBackgroundResource(if (i == 0) R.drawable.dot_focus else R.drawable.dot_normal)
            view.layoutParams = lp
            indicator.addView(view, i)
        }
    }


}

ScrollView

假如我們要實現一個拖拉效果的引導頁怎麼去做呢,這裏就要去使用 ScrollView 了,如果只是要單純的使用拖拉的話就可以直接使用 ScrollView 控件,假如說要在裏面加一些動畫效果的話就要自定義控件去完成這一操作了。
首先我們就要去定義一個自定義 View,因爲我們是上下滑動的,所以我們在 onScrollChanged 方法中只需要對垂直方向的偏移有所關心,傳遞進接口,交於我們的 Activity 中進行處理。
MyScrollView.kt

package com.xjh.bootpagedemo.scrollview

import android.content.Context
import android.util.AttributeSet
import android.widget.ScrollView

class MyScrollView(context: Context, attrs: AttributeSet?) : ScrollView(context, attrs) {

    private lateinit var onScrollViewListener: OnScrollChangeListener

    override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
        super.onScrollChanged(l, t, oldl, oldt)
        onScrollViewListener.onScrollChange(t, oldt)
    }

    interface OnScrollChangeListener {
        fun onScrollChange(top: Int, oldTop: Int)
    }

    fun setOnScrollChangeListener(onScrollChangeListener: OnScrollChangeListener) {
        this.onScrollViewListener = onScrollChangeListener
    }
}

然後直接去使用該控件,在裏面定義好所需要的背景
activity_scroll_view.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.xjh.bootpagedemo.scrollview.MyScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorAccent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_horizontal"
            android:orientation="vertical">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="100dp"
                android:text="歡迎大家哇"
                android:textSize="20sp"
                android:textStyle="bold" />

            <ImageView
                android:layout_width="150dp"
                android:layout_height="150dp"
                android:layout_marginTop="20dp"
                android:src="@mipmap/ic_launcher" />

            <ImageView
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_marginTop="50dp"
                android:src="@drawable/dot_white" />

            <ImageView
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_marginTop="50dp"
                android:src="@drawable/dot_white" />

            <ImageView
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_marginTop="50dp"
                android:src="@drawable/dot_white" />

            <ImageView
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_marginTop="50dp"
                android:src="@drawable/dot_white" />

            <ImageView
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_marginTop="50dp"
                android:src="@drawable/dot_white" />

            <ImageView
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_marginTop="50dp"
                android:src="@drawable/dot_white" />

            <LinearLayout
                android:id="@+id/animLLayout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="50dp"
                android:gravity="center"
                android:orientation="horizontal"
                android:visibility="invisible">

                <ImageView
                    android:layout_width="50dp"
                    android:layout_height="50dp"
                    android:layout_margin="20dp"
                    android:src="@drawable/dot_yellow" />

                <ImageView
                    android:layout_width="50dp"
                    android:layout_height="50dp"
                    android:layout_margin="20dp"
                    android:src="@drawable/dot_blue" />

                <ImageView
                    android:layout_width="50dp"
                    android:layout_height="50dp"
                    android:layout_margin="20dp"
                    android:src="@drawable/dot_black" />

            </LinearLayout>

            <ImageView
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_marginTop="50dp"
                android:src="@drawable/dot_white" />

            <ImageView
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_marginTop="50dp"
                android:src="@drawable/dot_white" />

            <Button
                android:id="@+id/btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="50dp"
                android:layout_marginBottom="30dp"
                android:gravity="center"
                android:text="進入主頁"
                android:textSize="22sp" />
        </LinearLayout>
    </com.xjh.bootpagedemo.scrollview.MyScrollView>

</RelativeLayout>

之後就直接使用這個控件,通過上下滑動的偏移量來判斷是上移還是下移,我在判斷的時候加上了對當前狀態的判斷,防止重複設置狀態。
ScrollViewActivity.kt

package com.xjh.bootpagedemo.scrollview

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.animation.AnimationUtils
import com.xjh.bootpagedemo.MainActivity
import com.xjh.bootpagedemo.R
import kotlinx.android.synthetic.main.activity_scroll_view.*
import kotlinx.android.synthetic.main.activity_view_flipper.*
import kotlinx.android.synthetic.main.activity_view_flipper.btn

class ScrollViewActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_scroll_view)

        btn.setOnClickListener {
            startActivity(Intent(this, MainActivity::class.java))
        }
        val context = this
        scrollView.setOnScrollChangeListener(object : MyScrollView.OnScrollChangeListener {
            override fun onScrollChange(top: Int, oldTop: Int) {
                if (top > oldTop && animLLayout.visibility == View.INVISIBLE) {
                    val anim = AnimationUtils.loadAnimation(context, R.anim.show)
                    animLLayout.visibility = View.VISIBLE
                    animLLayout.startAnimation(anim)
                } else if (top < oldTop && animLLayout.visibility == View.VISIBLE) {
                    val anim = AnimationUtils.loadAnimation(context, R.anim.close)
                    animLLayout.visibility = View.INVISIBLE
                    animLLayout.startAnimation(anim)
                }
            }
        })
    }
}


項目Github地址:傳送門

發佈了329 篇原創文章 · 獲贊 117 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章