image.png
上下
两部分,上部分是传统春联和福字
,代表对大家的新年祝福
,下部分是主要
功能模块,包含红包金额、新年幸运签和是与不是
。Kotlin
语言进行编写,涉及到的技术有:ConstraintLayout
、Drawable
、
自定义View
、Android动画
、Viewpager2
、字体的设置
和传感器的使用
。 创意来源
发红包
,哈哈哈,这回金额可以他们自己摇出来
,具有互动和随机性
比较好玩,为新年增添一份乐趣
!幸运签
是给大家的祝福
!选择困难
,所以是与不是
这个模块就是解决此类问题
添加的!动起来
呀,刚好传统的摇签
可以用手机摇一摇
来模拟效果
,活动手腕一举两得
(真是个好点子啊)!安卓手机
的小伙伴可以下载安装包 体验
一把,我是停不下来
!LinearLayout
设置方向vertical
,中间在用一个LinearLayout
设置方向horizontal
。 布局嵌套
,所以这也是我为什么采用ConstraintLayout
来实现的原因,如下图,只用了一层
。image.png
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
。
image.png
<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_weight
app:layout_constraintDimensionRatio="1:1"
必须相互依赖
才起作用。<com.android.springfestival.view.VerticalTextView
android: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.VerticalTextView
android: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.DiamondTextView
android: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
添加背景
,减少
图片资源的使用
,从而降低包体积
的大小
。image.png
对联
和描边金线
均来自ShapeDrawable
,代码如下<?xml version="1.0" encoding="utf-8"?>
<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
方法image.png
目录
,放入我们需要的字体
。使用这个字体
,并传给父类
。布局文件
中使用
,代码在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 == 0 | setCurrentItem(adapter.itemCount - 2, false) |
currentPosition == adapter.itemCount - 1 | setCurrentItem(1, false) |
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
实现的。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 SensorManager
sensor = 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年心想事成
,想法几经改版
,还好最后坚持
做了出来。