背景
如果我们的 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
![](/qrcode.jpg)
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地址:传送门