
上下两部分,上部分是传统春联和福字,代表对大家的新年祝福,下部分是主要功能模块,包含红包金额、新年幸运签和是与不是。Kotlin语言进行编写,涉及到的技术有:ConstraintLayout、Drawable、
自定义View、Android动画、Viewpager2、字体的设置和传感器的使用。创意来源发红包,哈哈哈,这回金额可以他们自己摇出来,具有互动和随机性比较好玩,为新年增添一份乐趣!幸运签是给大家的祝福!选择困难,所以是与不是这个模块就是解决此类问题添加的!动起来呀,刚好传统的摇签可以用手机摇一摇来模拟效果,活动手腕一举两得(真是个好点子啊)!安卓手机的小伙伴可以下载安装包 体验一把,我是停不下来!
LinearLayout设置方向vertical,中间在用一个LinearLayout设置方向horizontal。布局嵌套,所以这也是我为什么采用ConstraintLayout来实现的原因,如下图,只用了一层。
ConstraintLayout中的控件横竖两个方向都至少要选择一个进行约束,否则控件将在左上角进行摆放。top指顶部bottom指底部start指左边end指右边,上例子 <com.android.springfestival.view.SpringTextView
android:id="@+id/top"
android:layout_width="wrap_content"
android:layout_height="?actionBarSize"
android:background="@drawable/shape_red_solid"
android:gravity="center"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:text="金 虎 迎 福"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />横批的文字,可以看到,它的顶部和父布局的顶部相约束,左边和父布局的左边相约束,右边和父布局的右边相约束。横向居中需要左右都加约束,不需要的话,想让控件在哪个方向开始摆放,就让它约束到该方向,如横批靠顶部摆放。上部分占百分之七十,下部分占百分之三十Guideline控件,上下分的话设置orientation为horizontal,想要左右分改为vertical即可。layout_constraintGuide_percent属性用来设置上或左占多少,数值范围为0到1。
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.7" />对联和福字,我打算福字占百分之三十,剩下的各占百分之十,所以控件宽高都设置了0dp,即占满剩余空间,为它们设置横向的权重,1:3:1。app:layout_constraintHorizontal_weightapp:layout_constraintDimensionRatio="1:1"必须相互依赖才起作用。<com.android.springfestival.view.VerticalTextViewandroid:id="@+id/left"android:layout_width="0dp"android:layout_height="0dp"android:layout_marginLeft="5dp"android:background="@drawable/shape_red_solid"android:ems="1"android:paddingTop="10dp"android:paddingBottom="10dp"android:text="迎春迎福迎富贵"android:textSize="24sp"android:textStyle="bold"app:layout_constraintHorizontal_weight="1"app:layout_constraintBottom_toTopOf="@id/guideline"app:layout_constraintEnd_toStartOf="@id/ling"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/top" /><com.android.springfestival.view.VerticalTextViewandroid:id="@+id/right"android:layout_width="0dp"android:layout_height="0dp"android:layout_marginRight="5dp"android:background="@drawable/shape_red_solid"android:ems="1"android:paddingTop="10dp"android:paddingBottom="10dp"android:text="接财接福接平安"android:textSize="24sp"android:textStyle="bold"app:layout_constraintHorizontal_weight="1"app:layout_constraintBottom_toBottomOf="@id/guideline"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toEndOf="@id/ling"app:layout_constraintTop_toBottomOf="@id/top" /><com.android.springfestival.view.DiamondTextViewandroid:id="@+id/ling"android:layout_width="0dp"app:layout_constraintDimensionRatio="1:1"app:layout_constraintHorizontal_weight="3"android:layout_height="0dp"android:layout_margin="5dp"android:autoSizeTextType="uniform"android:gravity="center"android:text="福"android:textStyle="bold"app:layout_constraintBottom_toTopOf="@+id/OptionVp"app:layout_constraintEnd_toStartOf="@id/right"app:layout_constraintStart_toEndOf="@id/left"app:layout_constraintTop_toBottomOf="@id/top"/>ViewPager2和指示器采用权重,将剩余的空间按4:1进行分配,这里权重和LinearLayout的用法一致。<androidx.viewpager2.widget.ViewPager2
android:id="@+id/OptionVp"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/llPointContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/guideline"
app:layout_constraintVertical_weight="4" />
<LinearLayout
android:id="@+id/llPointContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="10dp"
android:gravity="center_horizontal"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/OptionVp"
app:layout_constraintVertical_weight="1" />ShapeDrawable添加背景,减少图片资源的使用,从而降低包体积的大小。
对联和描边金线均来自ShapeDrawable,代码如下<shape xmlns:android="http://schemas.android.com/apk/res/android"><stroke android:width="1dp" android:color="@color/colorGold" /><solid android:color="@color/colorRed" /></shape>字体不是系统自带的字体,我们要改变字体,最简单的方法是继承TextView,重写他的setTypeface方法
目录,放入我们需要的字体。使用这个字体,并传给父类。布局文件中使用,代码在ConstraintLayout章节中。class SpringTextView(context: Context?, attrs: AttributeSet?) :
AppCompatTextView(context, attrs) {
//重写设置字体方法
override fun setTypeface(tf: Typeface?) {
super.setTypeface(Typeface.createFromAsset(context.assets, "fonts/hwxk.ttf"))
}
}TextView可以用android:ems="1"达到竖直排列,但是紧贴在一起,不能均分非常不美观,所以我们继续继承TextView,自定义竖直均分的效果。重写onDraw方法,自己来进行文字的绘制。关键点在于算出每个文字之间的空隙(总的高度减去上下的padding和文字的宽度除以文字的个数减一)总的宽度减去文字的宽除以二)override fun onDraw(canvas: Canvas) {
paint.textSize = textSize
paint.apply {
typeface = Typeface.createFromAsset(context.assets,"fonts/hwxk.ttf")
}
var textLengthHeight = 0
val r = Rect()
val arr = IntArray(text.length)
canvasLength = measuredHeight - paddingTop - paddingBottom
if (!TextUtils.isEmpty(text) && text.length > 1) {
var i = 0
while (i < text.length) {
paint.getTextBounds(text.substring(i, i + 1), 0, 1, r)
textLengthHeight += (r.bottom - r.top)
arr[i] = r.bottom - r.top
i++
}
space = (canvasLength - textLengthHeight).toDouble() / (text.length - 1)
}
var arrlength = 0f
var i = 0
while (i < text.length) {
arrlength += arr[i]
if (i == 0) {
canvas.drawText(
text.substring(i, i + 1),
((width - r.right - r.left) / 2).toFloat(),
(i * space + arrlength).toFloat() + paddingTop,
paint
)
} else {
canvas.drawText(
text.substring(i, i + 1),
((width - r.right - r.left) / 2).toFloat(),
(i * space + arrlength).toFloat(),
paint
)
}
i++
}
}菱形的TextView系统也没给咱,咋办呢,继续自定义!重写onDraw,获取宽高,取最短的,利用Path画一个出来,在为TextView设置背景即可。画了两次,因为福字怎么能少了金边呢!override fun onDraw(canvas: Canvas) {super.onDraw(canvas)var min = min(width, height)var mPath = Path().apply { moveTo(0F, (min / 2).toFloat()); lineTo((min / 2).toFloat(), 0F); lineTo(min.toFloat(), (min / 2).toFloat()); lineTo((min / 2).toFloat(), min.toFloat()); close();}val bmp = Bitmap.createBitmap(min, min, Bitmap.Config.ARGB_8888)val c = Canvas(bmp)c.drawPath(mPath, paint)c.drawPath(mPath, paintStock)setBackgroundDrawable(BitmapDrawable(resources, bmp))add最后一张图val newList = arrayListOf<String>()
newList.add(pic[pic.size-1])for (item in pic) {
newList.add(item)
}
newList.add(pic[0])ViewPager2滑动到第0位和最后一位时的处理分别如下位置 | 处理 |
|---|---|
currentPosition == | setCurrentItem( |
currentPosition == | setCurrentItem( |
ViewPager2添加滑动监听代码如下
关键点在onPageScrollStateChanged方法bannerVp.registerOnPageChangeCallback(object :
ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
currentPosition = position
}
override fun onPageScrollStateChanged(state: Int) {
//只有在空闲状态,才让自动滚动
if (state == ViewPager2.SCROLL_STATE_IDLE) {
if (currentPosition == 0) {
bannerVp.setCurrentItem(adapter.itemCount - 2, false)
} else if (currentPosition == adapter.itemCount - 1) {
bannerVp.setCurrentItem(1, false)
}
}
}
})ShapeDrawable来实现的,代码如下<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:bottomRightRadius="50dp"
android:topLeftRadius="50dp" />
<stroke
android:width="1dp"
android:color="@color/colorGold" />
<solid android:color="#F02A2A" />
</shape>动态创建View,代码如下:private fun initIndicator(){
llPointContainer.removeAllViews()
for (index in 1..size-2) {
val view = View(this)
val layoutParams = LinearLayout.LayoutParams(10.dp.toInt(), 10.dp.toInt())
layoutParams.marginEnd = 8
layoutParams.marginStart = 8
view.layoutParams = layoutParams
llPointContainer.addView(view)
}
}更新指示器背景ViewPager2的滑动监听的onPageSelected方法中调用如下方法即可判断if (position <= llPointContainer.childCount) updateIndicator(position)private fun updateIndicator(position: Int){
llPointContainer.run {
for (index in 1..childCount) {
getChildAt(index - 1).background = resources.getDrawable(R.drawable.circlered)
}
if (position > 0) {
getChildAt(position - 1).background = resources.getDrawable(R.drawable.circlegold)
}
}
}ShapeDrawable实现的。ViewPager2一屏多页效果ViewPager的一屏多页有很大区别,ViewPager采用为给自身设置margin并设置clipChildren属性为false。ViewPager2则是通过给RecyclerView设置Padding和PageTransformer的方式来实现OptionVp.apply {
offscreenPageLimit=1
val recyclerView= getChildAt(0) as RecyclerView
recyclerView.apply {
val padding = resources.getDimensionPixelOffset(R.dimen.common_line_height) +
resources.getDimensionPixelOffset(R.dimen.common_line_height)
// setting padding on inner RecyclerView puts overscroll effect in the right place
setPadding(padding, 0, padding, 0)
clipToPadding = false
}
}PageTransformer了,它可以用来设置页面动画,还可以设置页面间距,间距和动画都要的话就要用到CompositePageTransformer了。上一条,设置了页面间距并且用到了缩放效果,那么来看一下具体代码。val compositePageTransformer = CompositePageTransformer()
compositePageTransformer.addTransformer(ScaleInTransformer())
compositePageTransformer.addTransformer(
MarginPageTransformer(
resources.getDimension(R.dimen.common_margin_middle).toInt()
)
)
OptionVp.setPageTransformer(compositePageTransformer)很香,快用起来吧。传感器Android中有很多传感器,这里我们用到的是加速度传感器,使用步骤如下:传感器管理者对象加速度传感器对象注册传感器(onCreate中调用)解除传感器(onDestory中调用)sensorManager = getSystemService(SENSOR_SERVICE) as SensorManagersensor = sensorManager!!.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)sensorManager!!.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL)sensorManager!!.unregisterListener(this)监听器之后在onSensorChanged方法中做业务的判断(这里采用获取event.values大于15),符合业务条件就调用震动并弹出提示框。override fun onSensorChanged(event: SensorEvent) {
/* 当传感器数值发生改变时调用的函数*/
val values: FloatArray = event.values
val x = values[0]
val y = values[1]
val z = values[2]
val minValue = 15
if(!isShake) {
if (Math.abs(x) > minValue || Math.abs(y) > minValue || Math.abs(z) > minValue) {
//开始震动
isShake = true
val pattern = longArrayOf(300, 500)
vibrator!!.vibrate(pattern, -1)
//开始动画效果
MyDialog(this)
.showDialog(currentPosition - 1)
}
}
}manifest文件中申请权限振动器管理者对象vibrate开启震动
<!-- 振动器使用权限-->
<uses-permission android:name="android.permission.VIBRATE"/>//获取振动器管理者对象
vibrator = getSystemService(VIBRATOR_SERVICE) as Vibrator//开启震动
val pattern = longArrayOf(300, 500)
vibrator!!.vibrate(pattern, -1)View动画给Dialog添加入场和退场动画。
*View动画有如平移、缩放、旋转和透明度,这里使用了缩放。标签 | 含义 |
|---|---|
interpolator | 指定动画插入器,常见的有加速减速插入器accelerate_decelerate_interpolator,加速插入器elerate_interpolator,减速插入器decelerate_interpolator。 |
pivotX | 横向动画起始位置,相对于屏幕的百分比,50%表示动画从屏幕中间开始 |
pivotY | 纵向动画起始位置,相对于屏幕的百分比,50%表示动画从屏幕中间开始 |
fromXScale | 横向动画开始前的缩放,0.0为不显示,1.0为正常大小 |
toXScale | 横向动画最终缩放的倍数,1.0为正常大小,大于1.0放大 |
fromYScale | 纵向动画开始前的缩放,0.0为不显示,1.0为正常大小 |
toYScale | 纵向动画最终缩放的倍数,1.0为正常大小,大于1.0放大 |
入场动画,和出场动画就更方便理解从零到一进行缩放。<!-- 弹出时动画 -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:interpolator="@android:anim/accelerate_interpolator"
android:fromXScale="0.0"
android:toXScale="1.0"
android:fromYScale="0.0"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="false"
android:duration="400"/>
</set><!-- 退出时动画效果 -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:interpolator="@android:anim/accelerate_interpolator"
android:fromXScale="1.0"
android:toXScale="0.0"
android:fromYScale="1.0"
android:toYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="false"
android:duration="400"/>
</set>一到零回退到中心位置。随机结果将答案写在本地,随机抽取展示。随机的代码在Kotlin中很简单如下(answerList.indices).random()祝各位工程师,虎年大吉,2022年心想事成,想法几经改版,差点流产,还好最后坚持做了出来。
写作不易,有问题也欢迎留言交流哦!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。