我是靠谱客的博主 悲凉柜子,这篇文章主要介绍Android 仿微信小程序入口动画,现在分享给大家,希望可以做个参考。

效果对比

微信原版

仿照效果

流程分析

自定义ViewGroup

整个布局是通过自定义ViewGroup来管理的,在自定义ViewGroup中,子布局一共有两个,一个是小程序布局,一个是会话列表布局,然后按照上下分别摆放就可以了。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package com.example.kotlindemo.widget.weixin import android.content.Context import android.content.res.Resources import android.util.AttributeSet import android.util.Log import android.view.MotionEvent import android.view.View import android.view.ViewGroup import androidx.core.view.ViewCompat import androidx.customview.widget.ViewDragHelper import com.example.kotlindemo.R import java.math.BigDecimal class WeiXinMainPullViewGroup @JvmOverloads constructor( context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : ViewGroup(context, attrs, defStyleAttr) { public var viewDragHelper: ViewDragHelper = ViewDragHelper.create(this, 0.5f, DragHandler()); var headerMaskView: WeiXinPullHeaderMaskView? = null var isOpen: Boolean = false; val NAVIGAATION_HEIGHT = 100 init { } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { for (index in 0 until childCount) { if (getChildAt(index) != headerMaskView) { getChildAt(index).layout(l, paddingTop, r, b) } } } override fun computeScroll() { if (viewDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } } override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { Log.i("TAG", "onInterceptTouchEvent: ${ev.action}") MotionEvent.ACTION_MOVE return true } override fun onTouchEvent(event: MotionEvent): Boolean { viewDragHelper.processTouchEvent(event) return true } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) measureChildren(widthMeasureSpec, heightMeasureSpec) } fun createMaskView() { if (headerMaskView == null) { headerMaskView = WeiXinPullHeaderMaskView(context, null, 0) addView(headerMaskView) } } inner class DragHandler : ViewDragHelper.Callback() { override fun tryCaptureView(child: View, pointerId: Int): Boolean { return child is WeiXinMainLayout; } override fun onViewDragStateChanged(state: Int) { super.onViewDragStateChanged(state) } /** * 设置进度,设置遮罩layout */ override fun onViewPositionChanged( changedView: View, left: Int, top: Int, dx: Int, dy: Int ) { createMaskView(); var programView = getChildAt(0) var divide = BigDecimal(top.toString()).divide( BigDecimal(measuredHeight - NAVIGAATION_HEIGHT), 4, BigDecimal.ROUND_HALF_UP ) divide = divide.multiply(BigDecimal("100")) divide = divide.multiply(BigDecimal("0.002")) divide = divide.add(BigDecimal("0.8")) if (!isOpen) { programView.scaleX = divide.toFloat() programView.scaleY = divide.toFloat() } else { programView.top = paddingTop + (-((measuredHeight - NAVIGAATION_HEIGHT) - top)) } headerMaskView!!.maxHeight = measuredHeight / 3 headerMaskView!!.layout(0, paddingTop, measuredWidth, top) headerMaskView!!.setProgress( top.toFloat() / ((measuredHeight - (NAVIGAATION_HEIGHT + paddingTop)) / 3) * 100, measuredHeight - (NAVIGAATION_HEIGHT + paddingTop) ) if (top == paddingTop) { isOpen = false } if (top == measuredHeight - NAVIGAATION_HEIGHT) { isOpen = true } } override fun onViewCaptured(capturedChild: View, activePointerId: Int) { super.onViewCaptured(capturedChild, activePointerId) var programView = getChildAt(0) programView.top = paddingTop; } /** * 释放 */ override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) { /** * 如果已经打开或者释放后小于屏幕三分之一,回到原位 */ if (isOpen or (releasedChild.top + paddingTop <= measuredHeight / 3)) { viewDragHelper.smoothSlideViewTo(releasedChild, 0, paddingTop); ViewCompat.postInvalidateOnAnimation(this@WeiXinMainPullViewGroup); return } viewDragHelper.smoothSlideViewTo(releasedChild, 0, measuredHeight - NAVIGAATION_HEIGHT); ViewCompat.postInvalidateOnAnimation(this@WeiXinMainPullViewGroup); } override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int { if (top <= paddingTop) { return paddingTop } return (child.top + dy / 1.3).toInt(); } } }

还要增加一个用来填充状态栏的View,他的高度是动态获取的,整体布局是RelativeLayout,因为可以方便的设置中间View在状态下面和在导航栏上面。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
class ViewUtils { companion object{ @JvmStatic fun getStatusBarHeight(resources: Resources): Int { var result = 0 val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") if (resourceId > 0) { result = resources.getDimensionPixelSize(resourceId) } return result } } }

小程序缩放比例值计算

然后要做的就是拖动View,可以借助ViewDragHelper来完成,当拖动会话布局的时候,小程序的布局开始做一个缩放比例动画,这个缩放值我在这是这样做的,因为不可能是从0开始,要从一个基础值开始,这个基础值就是0.8,那么剩下0.2的缩放值,就是从开始下拉算起,到整体的高度的百分比。

比如屏幕高度是1000,下拉到500的时候,那么这个缩放值就是0.1,在加上基础值0.8,计算方式如下,整体高度还要减去导航栏的高度。

复制代码
1
2
3
4
5
6
7
8
9
10
var divide = BigDecimal(top.toString()).divide(BigDecimal(measuredHeight-NAVIGAATION_HEIGHT), 4, BigDecimal.ROUND_HALF_UP) divide = divide.multiply(BigDecimal("100")) divide = divide.multiply(BigDecimal("0.002" )) divide = divide.add(BigDecimal("0.8")) if (!isOpen) { programView.scaleX = divide.toFloat() programView.scaleY = divide.toFloat() } else { programView.top = paddingTop + (-((measuredHeight - NAVIGAATION_HEIGHT) - top)) }

这里就注意细节了,下拉的时候,小程序布局是通过缩放呈现的,但是上滑关闭的时,小程序布局是和会话布局同时向上走的。

动画遮罩

这是比较麻烦的一步,就是绘制进度动画,也就是那三个圆点。

这个原点有三种状态,一是出现时从小到大,二是到一定大小后,分离出两个固定大小的圆,但是这两个圆比此时中间的要小,并且和下拉进度慢慢向两边扩撒,三是中间的圆开始缩小,直到和其余两个圈同等大小。

这里就要另一波细节了,当还在屏幕的三分之一下拉时,这个头部遮罩布局整体还是不透明的,但是到屏幕的三分之一时,这个布局的透明度开始从255到0运动。并且到达三分之一的时候,还要振动一下,并且只要振动过了,那么在手指未松开时,再次到达屏幕的三分之一时,不会产生振动。

还有一波细节,状态栏由于使用了View填充,所以,从屏幕三份之一后开始,这个View的透明度也要从255-0开始运动。

完整代码如下。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package com.example.kotlindemo.widget.weixin import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.os.VibrationEffect import android.os.Vibrator import android.util.AttributeSet import android.util.Log import android.view.View import androidx.core.content.ContextCompat import com.example.kotlindemo.MainActivity import com.example.kotlindemo.R class WeiXinPullHeaderMaskView @JvmOverloads constructor( context: Context?, attrs: AttributeSet?, defStyleAttr: Int ) : View(context, attrs, defStyleAttr) { var isVibrator: Boolean = false; var progress: Int = 0; var maxHeight: Int = 0; private val CIRCLE_MAX_SIZE = 32; var parentHeight=0; var paint = Paint() private val DEFAULT_CIRCLE_SIZE=8f; init { setBackgroundColor(Color.argb(255 , 239, 239, 239)) paint.alpha=255; paint.color = ContextCompat.getColor(context!!, R.color.circleColor) paint.isAntiAlias = true; } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) var value = height.toFloat() / maxHeight if (height <= maxHeight / 2) { canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), CIRCLE_MAX_SIZE * value, paint) } else { if (progress<100){ var diff = (value - 0.5f) * CIRCLE_MAX_SIZE canvas.drawCircle(((width / 2).toFloat()-((0.4f-value)*100)), (height / 2).toFloat(), DEFAULT_CIRCLE_SIZE, paint) canvas.drawCircle(((width / 2).toFloat()+((0.4f-value)*100)), (height / 2).toFloat(), DEFAULT_CIRCLE_SIZE, paint) if ((CIRCLE_MAX_SIZE * 0.5f) - diff<=DEFAULT_CIRCLE_SIZE){ canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), DEFAULT_CIRCLE_SIZE, paint) }else{ canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), (CIRCLE_MAX_SIZE * 0.5f) - diff, paint) } }else{ paint.alpha=getAlphaValue(); canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), DEFAULT_CIRCLE_SIZE, paint) canvas.drawCircle((width / 2).toFloat()-((0.4f)*100), (height / 2).toFloat(), DEFAULT_CIRCLE_SIZE, paint) canvas.drawCircle((width / 2).toFloat()+(((0.4f)*100)), (height / 2).toFloat(), DEFAULT_CIRCLE_SIZE, paint) } } } private fun getAlphaValue():Int{ val dc=parentHeight/3-ViewUtils.getStatusBarHeight(resources); val alpha=((height).toFloat()-dc)/(parentHeight-(dc)) return 255-(255*alpha).toInt() } private fun vibrator() { var vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { var createOneShot = VibrationEffect.createOneShot(7, 255) vibrator.vibrate(createOneShot) } else { vibrator.vibrate(7) } } fun setProgress(value: Float,parentHeight:Int) { this.progress = value.toInt(); this.parentHeight=parentHeight; if (value >= 100 && !isVibrator) { vibrator() isVibrator = true; } if (value < 100) { isVibrator = false; } if (progress>=100){ setBackgroundColor(Color.argb(getAlphaValue() , 239, 239, 239)) var mainActivity = context as MainActivity mainActivity.changeStatusBackgroundAlphaValue(getAlphaValue()) }else{ setBackgroundColor(Color.argb(255, 239, 239, 239)) } invalidate() } }

还有就是这三个原点是始终位于遮罩View中间的,绘制的时候只需要在中间绘制,遮罩View的高度会被外界View所更改。

MainActivity

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import android.graphics.Color import android.os.Build import android.os.Bundle import android.view.View import android.view.Window import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import com.example.kotlindemo.databinding.ActivityMainBinding import com.example.kotlindemo.widget.weixin.ChatSession import com.example.kotlindemo.widget.weixin.ChatSessionAdapter import com.example.kotlindemo.widget.weixin.ViewUtils class MainActivity : AppCompatActivity() { lateinit var binding: ActivityMainBinding; fun changeStatusBackgroundAlphaValue(value: Int){ binding.statusBar.setBackgroundColor(Color.argb(value, 239, 239, 239)) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main); var layoutParams = binding.statusBar.layoutParams layoutParams.height=ViewUtils.getStatusBarHeight(resources) binding.statusBar.layoutParams=layoutParams binding.wxMain.setPadding(0, ViewUtils.getStatusBarHeight(resources), 0, 0) if (Build.VERSION.SDK_INT >= 21) { val window: Window = window window.getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR ) window.setStatusBarColor(Color.TRANSPARENT) } val chatSessions= mutableListOf<ChatSession>() for (index in 0 .. 10){ chatSessions.add(ChatSession("https://img2.baidu.com/it/u=3538084390,1079314259&fm=26&fmt=auto&gp=0.jpg","马云","你来,我把公司给你","上午")) chatSessions.add(ChatSession("https://img0.baidu.com/it/u=273576249,1042072491&fm=26&fmt=auto&gp=0.jpg","奥巴马","哥哥在哪呢","上午")) chatSessions.add(ChatSession("https://img1.baidu.com/it/u=152902017,4157746361&fm=11&fmt=auto&gp=0.jpg","成龙","马上接你","上午")) chatSessions.add(ChatSession("https://img0.baidu.com/it/u=3789809038,289359647&fm=26&fmt=auto&gp=0.jpg","窃瓦辛格","我教你啊","上午")) } binding.chatList.adapter=ChatSessionAdapter(chatSessions,this) } }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data> </data> <RelativeLayout android:background="@drawable/program_background" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <com.example.kotlindemo.widget.weixin.WeiXinMainPullViewGroup android:paddingTop="40dp" android:layout_above="@+id/navigation" android:id="@+id/wx_main" android:layout_width="match_parent" android:layout_height="match_parent" > <com.example.kotlindemo.widget.weixin.WeiXinProgram android:paddingLeft="30dp" android:paddingRight="30dp" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:textSize="17sp" android:textColor="#C8C8C8" android:gravity="center" android:text="最近" android:layout_width="match_parent" android:layout_height="40dp"></TextView> <androidx.cardview.widget.CardView android:background="#424459" app:cardBackgroundColor="#424459" app:cardElevation="0dp" app:cardCornerRadius="8dp" android:layout_width="match_parent" android:layout_height="46dp"> <LinearLayout android:gravity="center" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:textSize="15sp" android:textColor="#C8C8C8" android:text="搜索小程序" android:gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView> </LinearLayout> </androidx.cardview.widget.CardView> <com.example.kotlindemo.widget.weixin.ProgramGridLayout android:layout_marginTop="20dp" android:layout_width="match_parent" android:layout_height="wrap_content"> </com.example.kotlindemo.widget.weixin.ProgramGridLayout> <com.example.kotlindemo.widget.weixin.ProgramGridLayout android:layout_marginTop="20dp" android:layout_width="match_parent" android:layout_height="wrap_content"> </com.example.kotlindemo.widget.weixin.ProgramGridLayout> </com.example.kotlindemo.widget.weixin.WeiXinProgram> <com.example.kotlindemo.widget.weixin.WeiXinMainLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="44dp" android:background="@color/navigation_color"> <TextView android:textStyle="bold" android:textSize="16sp" android:textColor="#000000" android:layout_centerInParent="true" android:gravity="center" android:text="微信(323)" android:layout_width="wrap_content" android:layout_height="match_parent"></TextView> <ImageView android:layout_marginRight="45dp" android:scaleType="center" android:layout_centerVertical="true" android:layout_alignParentRight="true" android:src="@drawable/ic_search" android:layout_width="28dp" android:layout_height="28dp"></ImageView> <ImageView android:layout_marginRight="10dp" android:scaleType="center" android:layout_centerVertical="true" android:layout_alignParentRight="true" android:src="@drawable/ic_add" android:layout_width="28dp" android:layout_height="28dp"> </ImageView> </RelativeLayout> <com.example.kotlindemo.widget.weixin.WeiXinChatSessionListView android:paddingLeft="15dp" android:paddingRight="15dp" android:dividerHeight="10dp" android:id="@+id/chat_list" android:background="#FBFAFA" android:layout_width="match_parent" android:layout_height="match_parent"> </com.example.kotlindemo.widget.weixin.WeiXinChatSessionListView> </com.example.kotlindemo.widget.weixin.WeiXinMainLayout> </com.example.kotlindemo.widget.weixin.WeiXinMainPullViewGroup> <LinearLayout android:background="@color/navigation_color" android:orientation="vertical" android:id="@+id/navigation" android:layout_alignParentBottom="true" android:layout_width="match_parent" android:layout_height="60dp"> </LinearLayout> <View android:background="@color/navigation_color" android:id="@+id/status_bar" android:layout_width="match_parent" android:layout_height="100dp"></View> </RelativeLayout> </layout>

以上就是Android 仿微信小程序入口动画的详细内容,更多关于Android 微信小程序入口动画的资料请关注靠谱客其它相关文章!

最后

以上就是悲凉柜子最近收集整理的关于Android 仿微信小程序入口动画的全部内容,更多相关Android内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(168)

评论列表共有 0 条评论

立即
投稿
返回
顶部