├── .gitignore
├── README.MD
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── yunlei
│ │ └── douyinlike
│ │ ├── MainActivity.kt
│ │ ├── adapter
│ │ ├── HomeViewPagerAdapter.kt
│ │ ├── ItemViewPagerAdapter.kt
│ │ └── MainViewPagerAdapter.kt
│ │ ├── entity
│ │ └── EventBusItem.kt
│ │ ├── fragment
│ │ ├── HomeFragment.kt
│ │ ├── InfoFragment.kt
│ │ ├── ItemFragment.kt
│ │ └── VideoFragment.kt
│ │ ├── utils
│ │ └── Expand.kt
│ │ └── widget
│ │ └── LikeLayout.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── ic_add_box_black_24dp.xml
│ ├── ic_arrow_back_black_24dp.xml
│ ├── ic_launcher_background.xml
│ ├── ic_live_tv_black_24dp.xml
│ ├── ic_message_black_24dp.xml
│ ├── ic_search_black_24dp.xml
│ └── ic_share_black_24dp.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── fragment_home.xml
│ ├── fragment_info.xml
│ ├── fragment_item.xml
│ ├── fragment_video.xml
│ └── layout_home_title.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_heart.png
│ ├── ic_heart_off.png
│ ├── ic_heart_on.png
│ ├── ic_launcher.png
│ ├── ic_launcher_round.png
│ ├── image_head_def.png
│ ├── image_info_demo.jpg
│ └── image_splash.jpg
│ ├── mipmap-xxhdpi
│ ├── ic_heart.png
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ └── file_paths.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── likebutton
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── github
│ │ └── like
│ │ ├── CircleView.java
│ │ ├── DotsView.java
│ │ ├── Icon.java
│ │ ├── IconType.java
│ │ ├── LikeButton.java
│ │ ├── OnAnimationEndListener.java
│ │ ├── OnLikeListener.java
│ │ └── Utils.java
│ └── res
│ ├── drawable
│ ├── heart_off.png
│ ├── heart_on.png
│ ├── star_off.png
│ ├── star_on.png
│ ├── thumb_off.png
│ └── thumb_on.png
│ ├── layout
│ └── likeview.xml
│ └── values
│ ├── attrs.xml
│ └── strings.xml
├── screenshot
├── 1.gif
├── 2.gif
├── 3.gif
├── 4.gif
└── 5.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # TouYin 仿抖音APP视频切换和点赞效果
2 |
3 | ### 1、ViewPager2
4 |
5 | 网上很多仿抖音视频切换的很多都是使用自定义竖方向的ViewPager或者使用RecyclerView+PagerSnapHelper实现。
6 | 但是这两种方式其实都有一定的缺陷:
7 |
8 | 1、但是ViewPager实现,生命周期有一定问题(ps:FragmentPagerAdapter在新版本中提供了BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT来解决这个问题),notifyDataSetChanged支持很不好。关于生命周期的更新可以参考[androidx中的Fragment懒加载方案](https://blog.csdn.net/qq_36486247/article/details/102531304)
9 |
10 | 2、而使用RecyclerView+PagerSnapHelper实现,需要自己处理生命周期。
11 |
12 | 其实我们可以使用androidx中提供的ViewPage2来实现这个功能。
13 | ```
14 | dependencies {
15 | implementation "androidx.viewpager2:viewpager2:1.0.0"
16 | }
17 | ```
18 | 查看源码,ViewPager2继承自ViewGroup,其中发现了三个比较重要的成员变量:
19 | ```java
20 | private LinearLayoutManager mLayoutManager;
21 | RecyclerView mRecyclerView;
22 | private PagerSnapHelper mPagerSnapHelper;
23 | ```
24 | 明眼人一看就知道了,ViewPager2的核心实现就是**RecyclerView+LinearLayoutManager+PagerSnapHelper**了,因为LinearLayoutManager本身就支持竖向和横向两种布局方式,所以ViewPager2也能很容易地支持这两种滚动方向了,而几乎不需要添加任何多余的代码。
25 |
26 | 使用上和老的ViewPager基本没啥却别,下面ViewPager2的几个新东西:
27 | >* 支持RTL布局
28 | >* 支持竖向滚动
29 | >* 完整支持notifyDataSetChanged
30 | >* FragmentStateAdapter替换了原来的 FragmentStatePagerAdapter
31 | >* RecyclerView.Adapter替换了原来的 PagerAdapter
32 | >* registerOnPageChangeCallback替换了原来的 addPageChangeListener (刚开始使用没看完源码,按照之前的set或者add找了半天,囧)
33 | >* public void setUserInputEnabled(boolean enabled)禁止滑动
34 | >* ...
35 |
36 | 关于更多ViewPager2的资料大家可以自行搜索。
37 |
38 | ### 2、视频播放器
39 |
40 | 本demo中使用的是[GSYVideoPlayer](https://github.com/CarGuo/GSYVideoPlayer),实际项目中可自行选择封装。demo中没有对其进行过多的处理,只是为了看效果,实际的抖音中有更多复杂的东西。
41 |
42 | ### 3、demo结构
43 |
44 | 
45 |
46 | 看最后实现的效果,**只是为了做实验,代码写的比较乱。**(gif有点大,加载比较慢)
47 |
48 | 
49 | 
50 |
51 | ### 4、无限上滑
52 |
53 | 借助ViewPager2的监听和adapter的notifyDataSetChanged即可实现
54 | ```kotlin
55 | viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
56 | override fun onPageSelected(position: Int) {
57 | super.onPageSelected(position)
58 | if (position==urlList.size-1){
59 | urlList.add(urlList[0])
60 | mPagerAdapter.notifyDataSetChanged()
61 | }
62 | }
63 | })
64 | ```
65 | ### 5、视频和作者主页切换控制
66 | ```kotlin
67 | override fun onResume() {
68 | super.onResume()
69 | if (mCurrentPosition > 0) {
70 | videoPlayer?.onVideoResume(false)
71 | } else {
72 | videoPlayer?.postDelayed({
73 | videoPlayer?.startPlayLogic()
74 | }, 200)
75 | }
76 | }
77 | override fun onPause() {
78 | super.onPause()
79 | likeLayout?.onPause()
80 | videoPlayer?.onVideoPause()
81 | mCurrentPosition = videoPlayer?.gsyVideoManager?.currentPosition ?: 0
82 | }
83 | ```
84 |
85 |
86 | ### 6、点赞效果
87 |
88 | 抖音的点赞效果是由右侧的桃心点赞和屏幕的点击构成,右侧的点击后为点赞状态并有点赞动画,再次点击取消点赞。
89 |
90 | 先来看看效果:
91 |
92 | 
93 |
94 | 此处我们观察效果基本和此前用过的一个三方库比较相似,此处就先又此代替,后面有时间再进行完善。
95 | 该库为[Like Button](https://github.com/jd-alexander/LikeButton),使用方式很简单,如下:
96 | ```xml
97 |
107 | ```
108 | **LikeButton**具有以下属性,使用时可自行去查看
109 | ```xml
110 |
122 | ```
123 | 设置**LikeButton**的监听事件:
124 | ```kotlin
125 | likeBtn.setOnLikeListener(object : OnLikeListener {
126 | override fun liked(p0: LikeButton?) {
127 | toast("已点赞~~")
128 | }
129 |
130 | override fun unLiked(p0: LikeButton?) {
131 | toast("取消点赞~~")
132 | }
133 |
134 | })
135 | ```
136 |
137 | 我们可以看一下点赞按钮的点赞效果
138 |
139 | 
140 |
141 | 和抖音的还是有点区别的,后续完善
142 |
143 | ### 7、屏幕点赞
144 |
145 | 分析抖音的屏幕点赞由几个部分组合而成(红心可自绘,也可以直接使用图片)
146 | > 1、刚开始显示的时候,有个由大到小缩放动画
147 |
148 | > 2、透明度变化
149 |
150 | > 3、向上平移
151 |
152 | > 4、由小到大的缩放
153 |
154 | > 5、刚开始显示的时候有个小小的偏移,避免每个红心在同一个位置
155 |
156 | #### 8、实现过程
157 | 获取红心
158 | ```kotlin
159 | private var icon: Drawable = resources.getDrawable(R.mipmap.ic_heart)
160 | ```
161 | 监听Touch事件,并在按下位置添加View
162 | ```kotlin
163 | override fun onTouchEvent(event: MotionEvent?): Boolean {
164 | if (event?.action == MotionEvent.ACTION_DOWN) { //按下时在Layout中生成红心
165 | val x = event.x
166 | val y = event.y
167 | addHeartView(x, y)
168 | onLikeListener()
169 | }
170 | return super.onTouchEvent(event)
171 | }
172 | ```
173 | 为红心添加一个随机的偏移(此处为-10~10)
174 | ```kotlin
175 | img.scaleType = ImageView.ScaleType.MATRIX
176 | val matrix = Matrix()
177 | matrix.postRotate(getRandomRotate()) //设置红心的微量偏移
178 | ```
179 | 设置一开始的缩放动画
180 | ```kotlin
181 | private fun getShowAnimSet(view: ImageView): AnimatorSet {
182 | // 缩放动画
183 | val scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1.2f, 1f)
184 | val scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1.2f, 1f)
185 | val animSet = AnimatorSet()
186 | animSet.playTogether(scaleX, scaleY)
187 | animSet.duration = 100
188 | return animSet
189 | }
190 | ```
191 | 设置慢慢消失时的动画
192 | ```kotlin
193 | private fun getHideAnimSet(view: ImageView): AnimatorSet {
194 | // 1.alpha动画
195 | val alpha = ObjectAnimator.ofFloat(view, "alpha", 1f, 0.1f)
196 | // 2.缩放动画
197 | val scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 2f)
198 | val scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 2f)
199 | // 3.translation动画
200 | val translation = ObjectAnimator.ofFloat(view, "translationY", 0f, -150f)
201 | val animSet = AnimatorSet()
202 | animSet.playTogether(alpha, scaleX, scaleY, translation)
203 | animSet.duration = 500
204 | return animSet
205 | }
206 | ```
207 | 设置动画关系,并且在动画结束后Remove该红心
208 | ```kotlin
209 | val animSet = getShowAnimSet(img)
210 | val hideSet = getHideAnimSet(img)
211 | animSet.start()
212 | animSet.addListener(object : AnimatorListenerAdapter() {
213 | override fun onAnimationEnd(animation: Animator?) {
214 | super.onAnimationEnd(animation)
215 | hideSet.start()
216 | }
217 | })
218 | hideSet.addListener(object : AnimatorListenerAdapter() {
219 | override fun onAnimationEnd(animation: Animator?) {
220 | super.onAnimationEnd(animation)
221 | removeView(img) //动画结束移除红心
222 | }
223 | })
224 | ```
225 | 区分单击和多次点击,单击的时候控制视频的暂停和播放,多次点击的时候实现点赞功能
226 | ```kotlin
227 | override fun onTouchEvent(event: MotionEvent?): Boolean {
228 | if (event?.action == MotionEvent.ACTION_DOWN) { //按下时在Layout中生成红心
229 | val x = event.x
230 | val y = event.y
231 | mClickCount++
232 | mHandler.removeCallbacksAndMessages(null)
233 | if (mClickCount >= 2) {
234 | addHeartView(x, y)
235 | onLikeListener()
236 | mHandler.sendEmptyMessageDelayed(1, 500)
237 | } else {
238 | mHandler.sendEmptyMessageDelayed(0, 500)
239 | }
240 |
241 | }
242 | return true
243 | }
244 |
245 | private fun pauseClick() {
246 | if (mClickCount == 1) {
247 | onPauseListener()
248 | }
249 | mClickCount = 0
250 | }
251 |
252 | fun onPause() {
253 | mClickCount = 0
254 | mHandler.removeCallbacksAndMessages(null)
255 | }
256 | ```
257 |
258 |
259 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'kotlin-android-extensions'
6 |
7 | android {
8 | compileSdkVersion 28
9 | buildToolsVersion "29.0.2"
10 | defaultConfig {
11 | applicationId "com.yunlei.douyinlike"
12 | minSdkVersion 19
13 | targetSdkVersion 28
14 | versionCode 1
15 | versionName "1.0.0"
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 |
18 | ndk {
19 | //设置支持的SO库架构
20 | abiFilters 'armeabi-v7a'
21 | }
22 | }
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 | }
30 |
31 | dependencies {
32 | implementation fileTree(dir: 'libs', include: ['*.jar'])
33 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
34 | implementation "org.jetbrains.anko:anko:$anko_version"
35 | implementation 'androidx.appcompat:appcompat:1.1.0'
36 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
37 | implementation 'androidx.legacy:legacy-support-v4:1.0.0'
38 | testImplementation 'junit:junit:4.12'
39 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
40 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
41 | implementation project(path: ':likebutton')
42 | implementation 'androidx.recyclerview:recyclerview:1.1.0'
43 | implementation 'com.google.android.material:material:1.2.0-alpha03'
44 | implementation 'com.shuyu:gsyVideoPlayer-java:7.1.2'
45 | implementation 'com.shuyu:gsyVideoPlayer-armv7a:7.1.2'
46 | implementation 'de.hdodenhof:circleimageview:3.0.1'
47 | implementation 'org.greenrobot:eventbus:3.1.1'
48 | // 基础依赖包,必须要依赖
49 | implementation 'com.gyf.immersionbar:immersionbar:3.0.0'
50 | // fragment快速实现(可选)
51 | implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0'
52 | // kotlin扩展(可选)
53 | implementation 'com.gyf.immersionbar:immersionbar-ktx:3.0.0'
54 | implementation 'androidx.core:core:1.2.0-rc01'
55 | }
56 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
29 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yunlei/douyinlike/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.yunlei.douyinlike
2 |
3 | import android.Manifest
4 | import android.content.pm.PackageManager
5 | import android.os.Bundle
6 | import android.widget.Toast
7 | import androidx.appcompat.app.AppCompatActivity
8 | import androidx.core.app.ActivityCompat
9 | import androidx.core.content.ContextCompat
10 | import com.gyf.immersionbar.ImmersionBar
11 | import com.yunlei.douyinlike.adapter.MainViewPagerAdapter
12 | import kotlinx.android.synthetic.main.activity_main.*
13 |
14 | class MainActivity : AppCompatActivity() {
15 |
16 |
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | ImmersionBar.with(this).init()
20 | setContentView(R.layout.activity_main)
21 | viewPager.adapter = MainViewPagerAdapter(this)
22 | viewPager.isUserInputEnabled = false
23 |
24 |
25 | val check = ContextCompat.checkSelfPermission(this, Manifest.permission_group.CALENDAR)
26 | if (check != PackageManager.PERMISSION_GRANTED) {
27 | ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE), 0x99)
28 | }
29 | }
30 |
31 | private fun toast(msg: String) {
32 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yunlei/douyinlike/adapter/HomeViewPagerAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.yunlei.douyinlike.adapter
2 |
3 | import androidx.fragment.app.Fragment
4 | import androidx.viewpager2.adapter.FragmentStateAdapter
5 | import com.yunlei.douyinlike.fragment.ItemFragment
6 |
7 | /**
8 | * @author Yun.Lei
9 | * @email waitshan@163.com
10 | * @date 2020/1/18
11 | */
12 | class HomeViewPagerAdapter(fragment: Fragment, private val list:MutableList) : FragmentStateAdapter(fragment) {
13 |
14 | override fun getItemCount(): Int = list.size
15 |
16 | override fun createFragment(position: Int): Fragment = ItemFragment.getNewInstance(list[position])
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/yunlei/douyinlike/adapter/ItemViewPagerAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.yunlei.douyinlike.adapter
2 |
3 | import androidx.fragment.app.Fragment
4 | import androidx.viewpager2.adapter.FragmentStateAdapter
5 | import com.yunlei.douyinlike.fragment.InfoFragment
6 | import com.yunlei.douyinlike.fragment.VideoFragment
7 |
8 | /**
9 | * @author Yun.Lei
10 | * @email waitshan@163.com
11 | * @date 2020/1/18
12 | */
13 | class ItemViewPagerAdapter(fragment: Fragment, private val url: String) : FragmentStateAdapter(fragment) {
14 |
15 | override fun getItemCount(): Int = 2
16 |
17 | override fun createFragment(position: Int): Fragment = when (position) {
18 | 0 -> VideoFragment.getNewInstance(url)
19 | else -> InfoFragment.getNewInstance(url)
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/yunlei/douyinlike/adapter/MainViewPagerAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.yunlei.douyinlike.adapter
2 |
3 | import androidx.fragment.app.Fragment
4 | import androidx.fragment.app.FragmentActivity
5 | import androidx.viewpager2.adapter.FragmentStateAdapter
6 | import com.yunlei.douyinlike.fragment.HomeFragment
7 |
8 | /**
9 | * @author Yun.Lei
10 | * @email waitshan@163.com
11 | * @date 2020/1/18
12 | */
13 | class MainViewPagerAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
14 |
15 | override fun getItemCount(): Int = 1
16 |
17 | override fun createFragment(position: Int): Fragment = HomeFragment.getNewInstance()
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/yunlei/douyinlike/entity/EventBusItem.kt:
--------------------------------------------------------------------------------
1 | package com.yunlei.douyinlike.entity
2 |
3 |
4 | data class ToolbarStateEvent(var isShow: Boolean)
5 |
6 | data class ChangePageEvent(var position: Int)
7 |
8 | data class ClearPositionEvent(var isClear: Boolean)
--------------------------------------------------------------------------------
/app/src/main/java/com/yunlei/douyinlike/fragment/HomeFragment.kt:
--------------------------------------------------------------------------------
1 | package com.yunlei.douyinlike.fragment
2 |
3 |
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.fragment.app.Fragment
9 | import androidx.viewpager2.widget.ViewPager2
10 | import com.gyf.immersionbar.ImmersionBar
11 | import com.shuyu.gsyvideoplayer.GSYVideoManager
12 | import com.yunlei.douyinlike.R
13 | import com.yunlei.douyinlike.adapter.HomeViewPagerAdapter
14 | import com.yunlei.douyinlike.entity.ClearPositionEvent
15 | import com.yunlei.douyinlike.entity.ToolbarStateEvent
16 | import kotlinx.android.synthetic.main.fragment_home.*
17 | import kotlinx.android.synthetic.main.layout_home_title.*
18 | import org.greenrobot.eventbus.EventBus
19 | import org.greenrobot.eventbus.Subscribe
20 | import org.greenrobot.eventbus.ThreadMode
21 |
22 | /**
23 | * @author Yun.Lei
24 | * @email waitshan@163.com
25 | * @date 2020/1/18
26 | */
27 | class HomeFragment : Fragment() {
28 |
29 | companion object {
30 | @JvmStatic
31 | fun getNewInstance(): HomeFragment = HomeFragment()
32 | }
33 |
34 | private val urlList = mutableListOf(
35 | "https://chengdu-1259068866.cos.ap-chengdu.myqcloud.com/3f4a65314ae666962dd1870d4d390d56.mp4",
36 | "http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4",
37 | "https://chengdu-1259068866.cos.ap-chengdu.myqcloud.com/b92d429b70dab52b830d99124d908f73.mp4",
38 | // "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",
39 | "https://chengdu-1259068866.cos.ap-chengdu.myqcloud.com/f2cd4ce84bc6c8948198c93d74856ffb.mp4",
40 | // "http://vfx.mtime.cn/Video/2019/03/19/mp4/190319212559089721.mp4",
41 | "https://chengdu-1259068866.cos.ap-chengdu.myqcloud.com/177d7bcff446ba6670089d0793a5a8df.mp4"
42 | )
43 | private lateinit var mPagerAdapter: HomeViewPagerAdapter
44 |
45 | override fun onCreate(savedInstanceState: Bundle?) {
46 | super.onCreate(savedInstanceState)
47 | EventBus.getDefault().register(this)
48 | }
49 |
50 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
51 | savedInstanceState: Bundle?): View? {
52 | // Inflate the layout for this fragment
53 | return inflater.inflate(R.layout.fragment_home, container, false)
54 | }
55 |
56 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
57 | super.onViewCreated(view, savedInstanceState)
58 | ImmersionBar.with(this).titleBar(toolBar).init()
59 | mPagerAdapter = HomeViewPagerAdapter(this, urlList)
60 | viewPager.adapter = mPagerAdapter
61 | viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
62 | override fun onPageSelected(position: Int) {
63 | super.onPageSelected(position)
64 | EventBus.getDefault().post(ClearPositionEvent(true))
65 | if (position==urlList.size-1){
66 | urlList.add(urlList[0])
67 | mPagerAdapter.notifyDataSetChanged()
68 | }
69 | }
70 | })
71 |
72 | }
73 |
74 | override fun onResume() {
75 | super.onResume()
76 | // GSYVideoManager.onResume()
77 | }
78 |
79 | @Subscribe(threadMode = ThreadMode.MAIN)
80 | fun onEventToolbarState(event:ToolbarStateEvent){
81 | toolBar.visibility = if (event.isShow){
82 | View.VISIBLE
83 | }else{
84 | View.GONE
85 | }
86 | }
87 |
88 | override fun onPause() {
89 | super.onPause()
90 | GSYVideoManager.onPause()
91 | }
92 |
93 |
94 | override fun onDestroy() {
95 | EventBus.getDefault().unregister(this)
96 | super.onDestroy()
97 | GSYVideoManager.releaseAllVideos()
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yunlei/douyinlike/fragment/InfoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.yunlei.douyinlike.fragment
2 |
3 |
4 | import android.os.Bundle
5 | import androidx.fragment.app.Fragment
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 |
10 | import com.yunlei.douyinlike.R
11 | import com.yunlei.douyinlike.entity.ChangePageEvent
12 | import com.yunlei.douyinlike.entity.ToolbarStateEvent
13 | import kotlinx.android.synthetic.main.fragment_info.*
14 | import org.greenrobot.eventbus.EventBus
15 | import org.jetbrains.anko.bundleOf
16 |
17 | /**
18 | * @author Yun.Lei
19 | * @email waitshan@163.com
20 | * @date 2020/1/18
21 | */
22 | class InfoFragment : Fragment() {
23 | companion object {
24 | @JvmStatic
25 | fun getNewInstance(url: String): InfoFragment {
26 | val fragment = InfoFragment()
27 | fragment.arguments = bundleOf("url" to url)
28 | return fragment
29 | }
30 | }
31 |
32 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
33 | savedInstanceState: Bundle?): View? {
34 | // Inflate the layout for this fragment
35 | return inflater.inflate(R.layout.fragment_info, container, false)
36 | }
37 |
38 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
39 | super.onViewCreated(view, savedInstanceState)
40 | backBtn.setOnClickListener {
41 | EventBus.getDefault().post(ChangePageEvent(0))
42 | }
43 | }
44 |
45 | override fun onResume() {
46 | super.onResume()
47 | EventBus.getDefault().post(ToolbarStateEvent(false))
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yunlei/douyinlike/fragment/ItemFragment.kt:
--------------------------------------------------------------------------------
1 | package com.yunlei.douyinlike.fragment
2 |
3 |
4 | import android.os.Bundle
5 | import androidx.fragment.app.Fragment
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 |
10 | import com.yunlei.douyinlike.R
11 | import com.yunlei.douyinlike.adapter.ItemViewPagerAdapter
12 | import com.yunlei.douyinlike.entity.ChangePageEvent
13 | import kotlinx.android.synthetic.main.fragment_item.*
14 | import org.greenrobot.eventbus.EventBus
15 | import org.greenrobot.eventbus.Subscribe
16 | import org.greenrobot.eventbus.ThreadMode
17 | import org.jetbrains.anko.bundleOf
18 |
19 | /**
20 | * @author Yun.Lei
21 | * @email waitshan@163.com
22 | * @date 2020/1/18
23 | */
24 | class ItemFragment : Fragment() {
25 |
26 | companion object {
27 | @JvmStatic
28 | fun getNewInstance(url: String): ItemFragment {
29 | val fragment = ItemFragment()
30 | fragment.arguments = bundleOf("url" to url)
31 | return fragment
32 | }
33 | }
34 |
35 | private var url = ""
36 |
37 | override fun onCreate(savedInstanceState: Bundle?) {
38 | super.onCreate(savedInstanceState)
39 | EventBus.getDefault().register(this)
40 | url = arguments?.getString("url") ?: ""
41 | }
42 |
43 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
44 | savedInstanceState: Bundle?): View? {
45 | // Inflate the layout for this fragment
46 | return inflater.inflate(R.layout.fragment_item, container, false)
47 | }
48 |
49 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
50 | super.onViewCreated(view, savedInstanceState)
51 | itemViewPager.adapter = ItemViewPagerAdapter(this, url)
52 | }
53 |
54 | @Subscribe(threadMode = ThreadMode.MAIN)
55 | fun onEventChangePage(event:ChangePageEvent){
56 | itemViewPager.currentItem = event.position
57 | }
58 |
59 | override fun onDestroy() {
60 | super.onDestroy()
61 | EventBus.getDefault().unregister(this)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yunlei/douyinlike/fragment/VideoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.yunlei.douyinlike.fragment
2 |
3 |
4 | import android.os.Bundle
5 | import android.util.Log
6 | import androidx.fragment.app.Fragment
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.View.GONE
10 | import android.view.ViewGroup
11 | import com.shuyu.gsyvideoplayer.GSYVideoManager
12 |
13 | import com.yunlei.douyinlike.R
14 | import com.yunlei.douyinlike.entity.ClearPositionEvent
15 | import com.yunlei.douyinlike.entity.ToolbarStateEvent
16 | import com.yunlei.douyinlike.utils.log
17 | import kotlinx.android.synthetic.main.fragment_video.*
18 | import org.greenrobot.eventbus.EventBus
19 | import org.greenrobot.eventbus.Subscribe
20 | import org.greenrobot.eventbus.ThreadMode
21 | import org.jetbrains.anko.bundleOf
22 |
23 | /**
24 | * @author Yun.Lei
25 | * @email waitshan@163.com
26 | * @date 2020/1/18
27 | */
28 | class VideoFragment : Fragment() {
29 |
30 |
31 | companion object {
32 | @JvmStatic
33 | fun getNewInstance(url: String): VideoFragment {
34 | val fragment = VideoFragment()
35 | fragment.arguments = bundleOf("url" to url)
36 | return fragment
37 | }
38 | }
39 |
40 | private var url = ""
41 | private var mCurrentPosition = 0L
42 |
43 | override fun onCreate(savedInstanceState: Bundle?) {
44 | super.onCreate(savedInstanceState)
45 | EventBus.getDefault().register(this)
46 | url = arguments?.getString("url") ?: ""
47 | }
48 |
49 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
50 | savedInstanceState: Bundle?): View? {
51 | // Inflate the layout for this fragment
52 | mCurrentPosition = 0
53 | return inflater.inflate(R.layout.fragment_video, container, false)
54 | }
55 |
56 |
57 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
58 | super.onViewCreated(view, savedInstanceState)
59 |
60 | videoPlayer?.apply {
61 | backButton.visibility = GONE
62 | titleTextView.visibility = GONE
63 | fullscreenButton.visibility = GONE
64 | isNeedShowWifiTip = false
65 | isLooping = true
66 | dismissControlTime = 0
67 | }
68 | videoPlayer.setUpLazy(url, false, null, null, "")
69 | // likeLayout.visibility = View.GONE
70 | likeLayout.onPauseListener = {
71 | if (videoPlayer.gsyVideoManager.isPlaying) {
72 | videoPlayer?.onVideoPause()
73 | } else {
74 | videoPlayer?.onVideoResume(false)
75 | }
76 | // videoPlayer.startButton.performClick()
77 | log("performClick")
78 | }
79 | likeLayout.onLikeListener = {
80 | if (!likeBtn.isLiked)
81 | likeBtn.performClick()
82 | }
83 |
84 | }
85 |
86 | override fun onResume() {
87 | super.onResume()
88 | EventBus.getDefault().post(ToolbarStateEvent(true))
89 | if (mCurrentPosition > 0) {
90 | videoPlayer?.onVideoResume(false)
91 | } else {
92 | videoPlayer?.postDelayed({
93 | videoPlayer?.startPlayLogic()
94 | }, 200)
95 | }
96 |
97 | log("onResume->mCurrentPosition:$mCurrentPosition")
98 | }
99 |
100 | @Subscribe(threadMode = ThreadMode.MAIN)
101 | fun onEventClearPosition(event: ClearPositionEvent) {
102 | if (event.isClear) {
103 | mCurrentPosition = 0
104 | }
105 | }
106 |
107 | override fun onPause() {
108 | super.onPause()
109 | likeLayout?.onPause()
110 | videoPlayer?.onVideoPause()
111 | mCurrentPosition = videoPlayer?.gsyVideoManager?.currentPosition ?: 0
112 |
113 | log("onPause")
114 | }
115 |
116 | override fun onStart() {
117 | super.onStart()
118 | log("onStart")
119 | }
120 |
121 | override fun onStop() {
122 | super.onStop()
123 | log("onStop")
124 | }
125 |
126 | override fun onDestroy() {
127 | super.onDestroy()
128 | EventBus.getDefault().unregister(this)
129 | }
130 |
131 |
132 | }
133 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yunlei/douyinlike/utils/Expand.kt:
--------------------------------------------------------------------------------
1 | package com.yunlei.douyinlike.utils
2 |
3 | import android.util.Log
4 | import androidx.fragment.app.Fragment
5 |
6 | /**
7 | * @author Yun.Lei
8 | * @email waitshan@163.com
9 | * @date 2020/1/18
10 | */
11 |
12 | fun Fragment.log(msg:String){
13 | Log.d(javaClass.simpleName,msg)
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/yunlei/douyinlike/widget/LikeLayout.kt:
--------------------------------------------------------------------------------
1 | package com.yunlei.douyinlike.widget
2 |
3 | import android.animation.Animator
4 | import android.animation.AnimatorListenerAdapter
5 | import android.animation.AnimatorSet
6 | import android.animation.ObjectAnimator
7 | import android.content.Context
8 | import android.graphics.Matrix
9 | import android.graphics.drawable.Drawable
10 | import android.os.Handler
11 | import android.os.Message
12 | import android.util.AttributeSet
13 | import android.view.MotionEvent
14 | import android.widget.FrameLayout
15 | import android.widget.ImageView
16 | import com.yunlei.douyinlike.R
17 | import java.lang.ref.WeakReference
18 | import java.util.*
19 |
20 | /**
21 | * 类名:LikeLayout
22 | * 作者:Yun.Lei
23 | * 功能:
24 | * 创建日期:2017-12-20 14:33
25 | * 修改人:
26 | * 修改时间:
27 | * 修改备注:
28 | */
29 | class LikeLayout : FrameLayout {
30 |
31 | var onLikeListener: () -> Unit = {} //屏幕点赞后,点赞按钮需同步点赞
32 | var onPauseListener: () -> Unit = {} //暂停 或者 继续播放
33 |
34 | constructor(context: Context?) : super(context)
35 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
36 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
37 |
38 | private var icon: Drawable = resources.getDrawable(R.mipmap.ic_heart)
39 | private var mClickCount = 0 //点击一次是暂停,多次是点赞
40 | private val mHandler = LikeLayoutHandler(this)
41 |
42 | init {
43 | clipChildren = false //避免旋转时红心被遮挡
44 | }
45 |
46 | override fun onTouchEvent(event: MotionEvent?): Boolean {
47 | if (event?.action == MotionEvent.ACTION_DOWN) { //按下时在Layout中生成红心
48 | val x = event.x
49 | val y = event.y
50 | mClickCount++
51 | mHandler.removeCallbacksAndMessages(null)
52 | if (mClickCount >= 2) {
53 | addHeartView(x, y)
54 | onLikeListener()
55 | mHandler.sendEmptyMessageDelayed(1, 500)
56 | } else {
57 | mHandler.sendEmptyMessageDelayed(0, 500)
58 | }
59 |
60 | }
61 | return true
62 | }
63 |
64 |
65 | private fun pauseClick() {
66 | if (mClickCount == 1) {
67 | onPauseListener()
68 | }
69 | mClickCount = 0
70 | }
71 |
72 | fun onPause() {
73 | mClickCount = 0
74 | mHandler.removeCallbacksAndMessages(null)
75 | }
76 |
77 | /**
78 | * 在Layout中添加红心并,播放消失动画
79 | */
80 | private fun addHeartView(x: Float, y: Float) {
81 | val lp = LayoutParams(icon.intrinsicWidth, icon.intrinsicHeight) //计算点击的点位红心的下部中间
82 | lp.leftMargin = (x - icon.intrinsicWidth / 2).toInt()
83 | lp.topMargin = (y - icon.intrinsicHeight).toInt()
84 | val img = ImageView(context)
85 | img.scaleType = ImageView.ScaleType.MATRIX
86 | val matrix = Matrix()
87 | matrix.postRotate(getRandomRotate()) //设置红心的微量偏移
88 | img.imageMatrix = matrix
89 | img.setImageDrawable(icon)
90 | img.layoutParams = lp
91 | addView(img)
92 | val animSet = getShowAnimSet(img)
93 | val hideSet = getHideAnimSet(img)
94 | animSet.start()
95 | animSet.addListener(object : AnimatorListenerAdapter() {
96 | override fun onAnimationEnd(animation: Animator?) {
97 | super.onAnimationEnd(animation)
98 | hideSet.start()
99 | }
100 | })
101 | hideSet.addListener(object : AnimatorListenerAdapter() {
102 | override fun onAnimationEnd(animation: Animator?) {
103 | super.onAnimationEnd(animation)
104 | removeView(img) //动画结束移除红心
105 | }
106 | })
107 | }
108 |
109 | /**
110 | * 刚点击的时候的一个缩放效果
111 | */
112 | private fun getShowAnimSet(view: ImageView): AnimatorSet {
113 | // 缩放动画
114 | val scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1.2f, 1f)
115 | val scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1.2f, 1f)
116 | val animSet = AnimatorSet()
117 | animSet.playTogether(scaleX, scaleY)
118 | animSet.duration = 100
119 | return animSet
120 | }
121 |
122 | /**
123 | * 缩放结束后到红心消失的效果
124 | */
125 | private fun getHideAnimSet(view: ImageView): AnimatorSet {
126 | // 1.alpha动画
127 | val alpha = ObjectAnimator.ofFloat(view, "alpha", 1f, 0.1f)
128 | // 2.缩放动画
129 | val scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 2f)
130 | val scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 2f)
131 | // 3.translation动画
132 | val translation = ObjectAnimator.ofFloat(view, "translationY", 0f, -150f)
133 | val animSet = AnimatorSet()
134 | animSet.playTogether(alpha, scaleX, scaleY, translation)
135 | animSet.duration = 500
136 | return animSet
137 | }
138 |
139 | /**
140 | * 生成一个随机的左右偏移量
141 | */
142 | private fun getRandomRotate(): Float = (Random().nextInt(20) - 10).toFloat()
143 |
144 | companion object {
145 | private class LikeLayoutHandler(view: LikeLayout) : Handler() {
146 | private val mView = WeakReference(view)
147 | override fun handleMessage(msg: Message?) {
148 | super.handleMessage(msg)
149 | when(msg?.what){
150 | 0-> mView.get()?.pauseClick()
151 | 1-> mView.get()?.onPause()
152 | }
153 |
154 | }
155 | }
156 | }
157 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_box_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_back_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_live_tv_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_message_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_share_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
24 |
25 |
29 |
30 |
33 |
34 |
38 |
39 |
42 |
43 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
15 |
16 |
22 |
23 |
24 |
25 |
33 |
34 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_video.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
18 |
19 |
25 |
26 |
30 |
31 |
40 |
41 |
48 |
49 |
54 |
55 |
62 |
63 |
68 |
69 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_home_title.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
22 |
23 |
29 |
30 |
33 |
34 |
41 |
42 |
43 |
47 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-xhdpi/ic_heart.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_heart_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-xhdpi/ic_heart_off.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_heart_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-xhdpi/ic_heart_on.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/image_head_def.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-xhdpi/image_head_def.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/image_info_demo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-xhdpi/image_info_demo.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/image_splash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-xhdpi/image_splash.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-xxhdpi/ic_heart.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #6495ED
4 | #6495ED
5 | #FF4081
6 | #555555
7 | #333333
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TouYin
3 | Hello blank fragment
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.3.61'
5 | ext.anko_version='0.10.8'
6 | repositories {
7 | google()
8 | jcenter()
9 |
10 | }
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:3.5.3'
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | google()
22 | jcenter()
23 | maven {url "https://jitpack.io"}
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | android.enableJetifier=true
13 | android.useAndroidX=true
14 | org.gradle.jvmargs=-Xmx1536m
15 |
16 | # When configured, Gradle will run in incubating parallel mode.
17 | # This option should only be used with decoupled projects. More details, visit
18 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
19 | # org.gradle.parallel=true
20 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jan 10 09:40:13 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/likebutton/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/likebutton/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 |
4 | android {
5 | compileSdkVersion 28
6 | buildToolsVersion "29.0.2"
7 |
8 | lintOptions {
9 | abortOnError false
10 | }
11 |
12 | defaultConfig {
13 | minSdkVersion 19
14 | targetSdkVersion 28
15 | versionCode 1
16 | versionName "1.0.0"
17 | vectorDrawables.useSupportLibrary = true
18 | }
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(dir: 'libs', include: ['*.jar'])
29 | testImplementation 'junit:junit:4.12'
30 | implementation 'androidx.appcompat:appcompat:1.1.0'
31 | }
32 |
--------------------------------------------------------------------------------
/likebutton/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Users\Joel\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/likebutton/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/likebutton/src/main/java/com/github/like/CircleView.java:
--------------------------------------------------------------------------------
1 | package com.github.like;
2 |
3 | import android.animation.ArgbEvaluator;
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.graphics.Canvas;
7 | import android.graphics.Paint;
8 | import android.graphics.PorterDuff;
9 | import android.graphics.PorterDuffXfermode;
10 | import androidx.annotation.ColorInt;
11 | import android.util.AttributeSet;
12 | import android.util.Property;
13 | import android.view.View;
14 |
15 | /**
16 | * Created by Miroslaw Stanek on 21.12.2015.
17 | * Modified by Joel Dean
18 | */
19 |
20 | public class CircleView extends View {
21 | private int START_COLOR = 0xFFFF5722;
22 | private int END_COLOR = 0xFFFFC107;
23 |
24 | private ArgbEvaluator argbEvaluator = new ArgbEvaluator();
25 |
26 | private Paint circlePaint = new Paint();
27 | private Paint maskPaint = new Paint();
28 |
29 | private Bitmap tempBitmap;
30 | private Canvas tempCanvas;
31 |
32 | private float outerCircleRadiusProgress = 0f;
33 | private float innerCircleRadiusProgress = 0f;
34 |
35 | private int width = 0;
36 | private int height = 0;
37 |
38 | private int maxCircleSize;
39 |
40 | public CircleView(Context context) {
41 | super(context);
42 | init();
43 | }
44 |
45 | public CircleView(Context context, AttributeSet attrs) {
46 | super(context, attrs);
47 | init();
48 | }
49 |
50 | public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
51 | super(context, attrs, defStyleAttr);
52 | init();
53 | }
54 |
55 |
56 | private void init() {
57 | circlePaint.setStyle(Paint.Style.FILL);
58 | circlePaint.setAntiAlias(true);
59 | maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
60 | maskPaint.setAntiAlias(true);
61 | }
62 |
63 | public void setSize(int width, int height) {
64 | this.width = width;
65 | this.height = height;
66 | invalidate();
67 | }
68 |
69 |
70 | @Override
71 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
72 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
73 |
74 | if (width != 0 && height != 0)
75 | setMeasuredDimension(width, height);
76 | }
77 |
78 |
79 | @Override
80 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
81 | super.onSizeChanged(w, h, oldw, oldh);
82 | maxCircleSize = w / 2;
83 | tempBitmap = Bitmap.createBitmap(getWidth(), getWidth(), Bitmap.Config.ARGB_8888);
84 | tempCanvas = new Canvas(tempBitmap);
85 | }
86 |
87 | @Override
88 | protected void onDraw(Canvas canvas) {
89 | super.onDraw(canvas);
90 | tempCanvas.drawColor(0xffffff, PorterDuff.Mode.CLEAR);
91 | tempCanvas.drawCircle(getWidth() / 2, getHeight() / 2, outerCircleRadiusProgress * maxCircleSize, circlePaint);
92 | tempCanvas.drawCircle(getWidth() / 2, getHeight() / 2, innerCircleRadiusProgress * maxCircleSize + 1, maskPaint);
93 | canvas.drawBitmap(tempBitmap, 0, 0, null);
94 | }
95 |
96 | public void setInnerCircleRadiusProgress(float innerCircleRadiusProgress) {
97 | this.innerCircleRadiusProgress = innerCircleRadiusProgress;
98 | postInvalidate();
99 | }
100 |
101 | public float getInnerCircleRadiusProgress() {
102 | return innerCircleRadiusProgress;
103 | }
104 |
105 | public void setOuterCircleRadiusProgress(float outerCircleRadiusProgress) {
106 | this.outerCircleRadiusProgress = outerCircleRadiusProgress;
107 | updateCircleColor();
108 | postInvalidate();
109 | }
110 |
111 | private void updateCircleColor() {
112 | float colorProgress = (float) Utils.clamp(outerCircleRadiusProgress, 0.5, 1);
113 | colorProgress = (float) Utils.mapValueFromRangeToRange(colorProgress, 0.5f, 1f, 0f, 1f);
114 | this.circlePaint.setColor((Integer) argbEvaluator.evaluate(colorProgress, START_COLOR, END_COLOR));
115 | }
116 |
117 | public float getOuterCircleRadiusProgress() {
118 | return outerCircleRadiusProgress;
119 | }
120 |
121 | public static final Property INNER_CIRCLE_RADIUS_PROGRESS =
122 | new Property(Float.class, "innerCircleRadiusProgress") {
123 | @Override
124 | public Float get(CircleView object) {
125 | return object.getInnerCircleRadiusProgress();
126 | }
127 |
128 | @Override
129 | public void set(CircleView object, Float value) {
130 | object.setInnerCircleRadiusProgress(value);
131 | }
132 | };
133 |
134 | public static final Property OUTER_CIRCLE_RADIUS_PROGRESS =
135 | new Property(Float.class, "outerCircleRadiusProgress") {
136 | @Override
137 | public Float get(CircleView object) {
138 | return object.getOuterCircleRadiusProgress();
139 | }
140 |
141 | @Override
142 | public void set(CircleView object, Float value) {
143 | object.setOuterCircleRadiusProgress(value);
144 | }
145 | };
146 |
147 | public void setStartColor(@ColorInt int color) {
148 | START_COLOR = color;
149 | invalidate();
150 | }
151 |
152 | public void setEndColor(@ColorInt int color) {
153 | END_COLOR = color;
154 | invalidate();
155 | }
156 | }
--------------------------------------------------------------------------------
/likebutton/src/main/java/com/github/like/DotsView.java:
--------------------------------------------------------------------------------
1 | package com.github.like;
2 |
3 |
4 | import android.animation.ArgbEvaluator;
5 | import android.content.Context;
6 | import android.graphics.Canvas;
7 | import android.graphics.Paint;
8 | import androidx.annotation.ColorInt;
9 | import android.util.AttributeSet;
10 | import android.util.Property;
11 | import android.view.View;
12 |
13 | /**
14 | * Created by Miroslaw Stanek on 21.12.2015.
15 | * Modified by Joel Dean
16 | */
17 | public class DotsView extends View {
18 | private static final int DOTS_COUNT = 7;
19 | private static final int OUTER_DOTS_POSITION_ANGLE = 51;
20 |
21 | private int COLOR_1 = 0xFFFFC107;
22 | private int COLOR_2 = 0xFFFF9800;
23 | private int COLOR_3 = 0xFFFF5722;
24 | private int COLOR_4 = 0xFFF44336;
25 |
26 | private int width = 0;
27 | private int height = 0;
28 |
29 | private final Paint[] circlePaints = new Paint[4];
30 |
31 | private int centerX;
32 | private int centerY;
33 |
34 | private float maxOuterDotsRadius;
35 | private float maxInnerDotsRadius;
36 | private float maxDotSize;
37 |
38 | private float currentProgress = 0;
39 |
40 | private float currentRadius1 = 0;
41 | private float currentDotSize1 = 0;
42 |
43 | private float currentDotSize2 = 0;
44 | private float currentRadius2 = 0;
45 |
46 | private ArgbEvaluator argbEvaluator = new ArgbEvaluator();
47 |
48 | public DotsView(Context context) {
49 | super(context);
50 | init();
51 | }
52 |
53 | public DotsView(Context context, AttributeSet attrs) {
54 | super(context, attrs);
55 | init();
56 | }
57 |
58 | public DotsView(Context context, AttributeSet attrs, int defStyleAttr) {
59 | super(context, attrs, defStyleAttr);
60 | init();
61 | }
62 |
63 |
64 | private void init() {
65 | for (int i = 0; i < circlePaints.length; i++) {
66 | circlePaints[i] = new Paint();
67 | circlePaints[i].setStyle(Paint.Style.FILL);
68 | circlePaints[i].setAntiAlias(true);
69 | }
70 | }
71 |
72 | @Override
73 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
74 | super.onSizeChanged(w, h, oldw, oldh);
75 | centerX = w / 2;
76 | centerY = h / 2;
77 | maxDotSize = 5;
78 | maxOuterDotsRadius = w / 2 - maxDotSize * 2;
79 | maxInnerDotsRadius = 0.8f * maxOuterDotsRadius;
80 | }
81 |
82 | @Override
83 | protected void onDraw(Canvas canvas) {
84 | drawOuterDotsFrame(canvas);
85 | drawInnerDotsFrame(canvas);
86 | }
87 |
88 | private void drawOuterDotsFrame(Canvas canvas) {
89 | for (int i = 0; i < DOTS_COUNT; i++) {
90 | int cX = (int) (centerX + currentRadius1 * Math.cos(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));
91 | int cY = (int) (centerY + currentRadius1 * Math.sin(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));
92 | canvas.drawCircle(cX, cY, currentDotSize1, circlePaints[i % circlePaints.length]);
93 | }
94 | }
95 |
96 | private void drawInnerDotsFrame(Canvas canvas) {
97 | for (int i = 0; i < DOTS_COUNT; i++) {
98 | int cX = (int) (centerX + currentRadius2 * Math.cos((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180));
99 | int cY = (int) (centerY + currentRadius2 * Math.sin((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180));
100 | canvas.drawCircle(cX, cY, currentDotSize2, circlePaints[(i + 1) % circlePaints.length]);
101 | }
102 | }
103 |
104 | public void setCurrentProgress(float currentProgress) {
105 | this.currentProgress = currentProgress;
106 |
107 | updateInnerDotsPosition();
108 | updateOuterDotsPosition();
109 | updateDotsPaints();
110 | updateDotsAlpha();
111 |
112 | postInvalidate();
113 | }
114 |
115 | public float getCurrentProgress() {
116 | return currentProgress;
117 | }
118 |
119 | private void updateInnerDotsPosition() {
120 | if (currentProgress < 0.3f) {
121 | this.currentRadius2 = (float) Utils.mapValueFromRangeToRange(currentProgress, 0, 0.3f, 0.f, maxInnerDotsRadius);
122 | } else {
123 | this.currentRadius2 = maxInnerDotsRadius;
124 | }
125 | if (currentProgress == 0) {
126 | this.currentDotSize2 = 0;
127 | } else if (currentProgress < 0.2) {
128 | this.currentDotSize2 = maxDotSize;
129 | } else if (currentProgress < 0.5) {
130 | this.currentDotSize2 = (float) Utils.mapValueFromRangeToRange(currentProgress, 0.2f, 0.5f, maxDotSize, 0.3 * maxDotSize);
131 | } else {
132 | this.currentDotSize2 = (float) Utils.mapValueFromRangeToRange(currentProgress, 0.5f, 1f, maxDotSize * 0.3f, 0);
133 | }
134 |
135 | }
136 |
137 | private void updateOuterDotsPosition() {
138 | if (currentProgress < 0.3f) {
139 | this.currentRadius1 = (float) Utils.mapValueFromRangeToRange(currentProgress, 0.0f, 0.3f, 0, maxOuterDotsRadius * 0.8f);
140 | } else {
141 | this.currentRadius1 = (float) Utils.mapValueFromRangeToRange(currentProgress, 0.3f, 1f, 0.8f * maxOuterDotsRadius, maxOuterDotsRadius);
142 | }
143 | if (currentProgress == 0) {
144 | this.currentDotSize1 = 0;
145 | } else if (currentProgress < 0.7) {
146 | this.currentDotSize1 = maxDotSize;
147 | } else {
148 | this.currentDotSize1 = (float) Utils.mapValueFromRangeToRange(currentProgress, 0.7f, 1f, maxDotSize, 0);
149 | }
150 | }
151 |
152 | private void updateDotsPaints() {
153 | if (currentProgress < 0.5f) {
154 | float progress = (float) Utils.mapValueFromRangeToRange(currentProgress, 0f, 0.5f, 0, 1f);
155 | circlePaints[0].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_1, COLOR_2));
156 | circlePaints[1].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_2, COLOR_3));
157 | circlePaints[2].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_3, COLOR_4));
158 | circlePaints[3].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_4, COLOR_1));
159 | } else {
160 | float progress = (float) Utils.mapValueFromRangeToRange(currentProgress, 0.5f, 1f, 0, 1f);
161 | circlePaints[0].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_2, COLOR_3));
162 | circlePaints[1].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_3, COLOR_4));
163 | circlePaints[2].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_4, COLOR_1));
164 | circlePaints[3].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_1, COLOR_2));
165 | }
166 | }
167 |
168 | public void setColors(@ColorInt int primaryColor, @ColorInt int secondaryColor) {
169 | COLOR_1 = primaryColor;
170 | COLOR_2 = secondaryColor;
171 | COLOR_3 = primaryColor;
172 | COLOR_4 = secondaryColor;
173 | invalidate();
174 | }
175 |
176 | private void updateDotsAlpha() {
177 | float progress = (float) Utils.clamp(currentProgress, 0.6f, 1f);
178 | int alpha = (int) Utils.mapValueFromRangeToRange(progress, 0.6f, 1f, 255, 0);
179 | circlePaints[0].setAlpha(alpha);
180 | circlePaints[1].setAlpha(alpha);
181 | circlePaints[2].setAlpha(alpha);
182 | circlePaints[3].setAlpha(alpha);
183 | }
184 |
185 | public void setSize(int width, int height) {
186 | this.width = width;
187 | this.height = height;
188 | invalidate();
189 | }
190 |
191 | @Override
192 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
193 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
194 |
195 | if (width != 0 && height != 0)
196 | setMeasuredDimension(width, height);
197 | }
198 |
199 |
200 | public static final Property DOTS_PROGRESS = new Property(Float.class, "dotsProgress") {
201 | @Override
202 | public Float get(DotsView object) {
203 | return object.getCurrentProgress();
204 | }
205 |
206 | @Override
207 | public void set(DotsView object, Float value) {
208 | object.setCurrentProgress(value);
209 | }
210 | };
211 | }
--------------------------------------------------------------------------------
/likebutton/src/main/java/com/github/like/Icon.java:
--------------------------------------------------------------------------------
1 | package com.github.like;
2 |
3 | import androidx.annotation.DrawableRes;
4 |
5 | /**
6 | * Created by Joel on 23/12/2015.
7 | */
8 | public class Icon {
9 | private int onIconResourceId;
10 | private int offIconResourceId;
11 | private IconType iconType;
12 |
13 | public Icon(@DrawableRes int onIconResourceId,@DrawableRes int offIconResourceId, IconType iconType) {
14 | this.onIconResourceId = onIconResourceId;
15 | this.offIconResourceId = offIconResourceId;
16 | this.iconType = iconType;
17 | }
18 |
19 | public int getOffIconResourceId() {
20 | return offIconResourceId;
21 | }
22 |
23 | public void setOffIconResourceId(@DrawableRes int offIconResourceId) {
24 | this.offIconResourceId = offIconResourceId;
25 | }
26 |
27 | public int getOnIconResourceId() {
28 | return onIconResourceId;
29 | }
30 |
31 | public void setOnIconResourceId(@DrawableRes int onIconResourceId) {
32 | this.onIconResourceId = onIconResourceId;
33 | }
34 |
35 | public IconType getIconType() {
36 | return iconType;
37 | }
38 |
39 | public void setIconType(IconType iconType) {
40 | this.iconType = iconType;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/likebutton/src/main/java/com/github/like/IconType.java:
--------------------------------------------------------------------------------
1 | package com.github.like;
2 |
3 | /**
4 | * Created by Joel on 23/12/2015.
5 | */
6 | public enum IconType {
7 | Heart,
8 | Thumb,
9 | Star
10 | }
11 |
--------------------------------------------------------------------------------
/likebutton/src/main/java/com/github/like/LikeButton.java:
--------------------------------------------------------------------------------
1 | package com.github.like;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.AnimatorSet;
6 | import android.animation.ObjectAnimator;
7 | import android.content.Context;
8 | import android.content.res.TypedArray;
9 | import android.graphics.drawable.Drawable;
10 | import androidx.annotation.ColorInt;
11 | import androidx.annotation.ColorRes;
12 | import androidx.annotation.DrawableRes;
13 | import androidx.core.content.ContextCompat;
14 | import android.util.AttributeSet;
15 | import android.view.LayoutInflater;
16 | import android.view.MotionEvent;
17 | import android.view.View;
18 | import android.view.animation.AccelerateDecelerateInterpolator;
19 | import android.view.animation.DecelerateInterpolator;
20 | import android.view.animation.OvershootInterpolator;
21 | import android.widget.FrameLayout;
22 | import android.widget.ImageView;
23 |
24 | import com.github.like.view.R;
25 |
26 | import java.util.List;
27 |
28 |
29 | public class LikeButton extends FrameLayout implements View.OnClickListener {
30 | private static final DecelerateInterpolator DECCELERATE_INTERPOLATOR = new DecelerateInterpolator();
31 | private static final AccelerateDecelerateInterpolator ACCELERATE_DECELERATE_INTERPOLATOR = new AccelerateDecelerateInterpolator();
32 | private static final OvershootInterpolator OVERSHOOT_INTERPOLATOR = new OvershootInterpolator(4);
33 |
34 | private ImageView icon;
35 | private DotsView dotsView;
36 | private CircleView circleView;
37 | private Icon currentIcon;
38 | private OnLikeListener likeListener;
39 | private OnAnimationEndListener animationEndListener;
40 | private int circleStartColor;
41 | private int circleEndColor;
42 | private int iconSize;
43 |
44 |
45 | private float animationScaleFactor;
46 |
47 | private boolean isChecked;
48 |
49 |
50 | private boolean isEnabled;
51 | private AnimatorSet animatorSet;
52 |
53 | private Drawable likeDrawable;
54 | private Drawable unLikeDrawable;
55 |
56 | public LikeButton(Context context) {
57 | this(context, null);
58 | }
59 |
60 | public LikeButton(Context context, AttributeSet attrs) {
61 | this(context, attrs, 0);
62 | }
63 |
64 | public LikeButton(Context context, AttributeSet attrs, int defStyleAttr) {
65 | super(context, attrs, defStyleAttr);
66 |
67 | if(!isInEditMode())
68 | init(context, attrs, defStyleAttr);
69 | }
70 |
71 | /**
72 | * Does all the initial setup of the button such as retrieving all the attributes that were
73 | * set in xml and inflating the like button's view and initial state.
74 | *
75 | * @param context
76 | * @param attrs
77 | * @param defStyle
78 | */
79 | private void init(Context context, AttributeSet attrs, int defStyle) {
80 | LayoutInflater.from(getContext()).inflate(R.layout.likeview, this, true);
81 | icon = findViewById(R.id.icon);
82 | dotsView = findViewById(R.id.dots);
83 | circleView = findViewById(R.id.circle);
84 |
85 | final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LikeButton, defStyle, 0);
86 |
87 | iconSize = array.getDimensionPixelSize(R.styleable.LikeButton_icon_size, -1);
88 | if (iconSize == -1)
89 | iconSize = 40;
90 |
91 | String iconType = array.getString(R.styleable.LikeButton_icon_type);
92 |
93 | likeDrawable = getDrawableFromResource(array, R.styleable.LikeButton_like_drawable);
94 |
95 | if (likeDrawable != null)
96 | setLikeDrawable(likeDrawable);
97 |
98 | unLikeDrawable = getDrawableFromResource(array, R.styleable.LikeButton_unlike_drawable);
99 |
100 | if (unLikeDrawable != null)
101 | setUnlikeDrawable(unLikeDrawable);
102 |
103 | if (iconType != null)
104 | if (!iconType.isEmpty())
105 | currentIcon = parseIconType(iconType);
106 |
107 |
108 | circleStartColor = array.getColor(R.styleable.LikeButton_circle_start_color, 0);
109 |
110 | if (circleStartColor != 0)
111 | circleView.setStartColor(circleStartColor);
112 |
113 | circleEndColor = array.getColor(R.styleable.LikeButton_circle_end_color, 0);
114 |
115 | if (circleEndColor != 0)
116 | circleView.setEndColor(circleEndColor);
117 |
118 | int dotPrimaryColor = array.getColor(R.styleable.LikeButton_dots_primary_color, 0);
119 | int dotSecondaryColor = array.getColor(R.styleable.LikeButton_dots_secondary_color, 0);
120 |
121 | if (dotPrimaryColor != 0 && dotSecondaryColor != 0) {
122 | dotsView.setColors(dotPrimaryColor, dotSecondaryColor);
123 | }
124 |
125 |
126 | if (likeDrawable == null && unLikeDrawable == null) {
127 | if (currentIcon != null) {
128 | setIcon();
129 | } else {
130 | setIcon(IconType.Heart);
131 | }
132 | }
133 |
134 | setEnabled(array.getBoolean(R.styleable.LikeButton_is_enabled, true));
135 | Boolean status = array.getBoolean(R.styleable.LikeButton_liked, false);
136 | setAnimationScaleFactor(array.getFloat(R.styleable.LikeButton_anim_scale_factor, 3));
137 | setLiked(status);
138 | setOnClickListener(this);
139 | array.recycle();
140 | }
141 |
142 | private Drawable getDrawableFromResource(TypedArray array, int styleableIndexId) {
143 | int id = array.getResourceId(styleableIndexId, -1);
144 |
145 | return (-1 != id) ? ContextCompat.getDrawable(getContext(), id) : null;
146 | }
147 |
148 | /**
149 | * This triggers the entire functionality of the button such as icon changes,
150 | * animations, listeners etc.
151 | *
152 | * @param v
153 | */
154 | @Override
155 | public void onClick(View v) {
156 |
157 | if (!isEnabled)
158 | return;
159 |
160 | isChecked = !isChecked;
161 |
162 | icon.setImageDrawable(isChecked ? likeDrawable : unLikeDrawable);
163 |
164 | if (likeListener != null) {
165 | if (isChecked) {
166 | likeListener.liked(this);
167 | } else {
168 | likeListener.unLiked(this);
169 | }
170 | }
171 |
172 | if (animatorSet != null) {
173 | animatorSet.cancel();
174 | }
175 |
176 | if (isChecked) {
177 | icon.animate().cancel();
178 | icon.setScaleX(0);
179 | icon.setScaleY(0);
180 | circleView.setInnerCircleRadiusProgress(0);
181 | circleView.setOuterCircleRadiusProgress(0);
182 | dotsView.setCurrentProgress(0);
183 |
184 | animatorSet = new AnimatorSet();
185 |
186 | ObjectAnimator outerCircleAnimator = ObjectAnimator.ofFloat(circleView, CircleView.OUTER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
187 | outerCircleAnimator.setDuration(250);
188 | outerCircleAnimator.setInterpolator(DECCELERATE_INTERPOLATOR);
189 |
190 | ObjectAnimator innerCircleAnimator = ObjectAnimator.ofFloat(circleView, CircleView.INNER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
191 | innerCircleAnimator.setDuration(200);
192 | innerCircleAnimator.setStartDelay(200);
193 | innerCircleAnimator.setInterpolator(DECCELERATE_INTERPOLATOR);
194 |
195 | ObjectAnimator starScaleYAnimator = ObjectAnimator.ofFloat(icon, ImageView.SCALE_Y, 0.2f, 1f);
196 | starScaleYAnimator.setDuration(350);
197 | starScaleYAnimator.setStartDelay(250);
198 | starScaleYAnimator.setInterpolator(OVERSHOOT_INTERPOLATOR);
199 |
200 | ObjectAnimator starScaleXAnimator = ObjectAnimator.ofFloat(icon, ImageView.SCALE_X, 0.2f, 1f);
201 | starScaleXAnimator.setDuration(350);
202 | starScaleXAnimator.setStartDelay(250);
203 | starScaleXAnimator.setInterpolator(OVERSHOOT_INTERPOLATOR);
204 |
205 | ObjectAnimator dotsAnimator = ObjectAnimator.ofFloat(dotsView, DotsView.DOTS_PROGRESS, 0, 1f);
206 | dotsAnimator.setDuration(900);
207 | dotsAnimator.setStartDelay(50);
208 | dotsAnimator.setInterpolator(ACCELERATE_DECELERATE_INTERPOLATOR);
209 |
210 | animatorSet.playTogether(
211 | outerCircleAnimator,
212 | innerCircleAnimator,
213 | starScaleYAnimator,
214 | starScaleXAnimator,
215 | dotsAnimator
216 | );
217 |
218 | animatorSet.addListener(new AnimatorListenerAdapter() {
219 | @Override
220 | public void onAnimationCancel(Animator animation) {
221 | circleView.setInnerCircleRadiusProgress(0);
222 | circleView.setOuterCircleRadiusProgress(0);
223 | dotsView.setCurrentProgress(0);
224 | icon.setScaleX(1);
225 | icon.setScaleY(1);
226 | }
227 |
228 | @Override public void onAnimationEnd(Animator animation) {
229 | if(animationEndListener != null) {
230 | animationEndListener.onAnimationEnd(LikeButton.this);
231 | }
232 | }
233 | });
234 |
235 | animatorSet.start();
236 | }
237 | }
238 |
239 | /**
240 | * Used to trigger the scale animation that takes places on the
241 | * icon when the button is touched.
242 | *
243 | * @param event
244 | * @return
245 | */
246 | @Override
247 | public boolean onTouchEvent(MotionEvent event) {
248 | if (!isEnabled)
249 | return true;
250 |
251 | switch (event.getAction()) {
252 | case MotionEvent.ACTION_DOWN:
253 | /*
254 | Commented out this line and moved the animation effect to the action up event due to
255 | conflicts that were occurring when library is used in sliding type views.
256 |
257 | icon.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).setInterpolator(DECCELERATE_INTERPOLATOR);
258 | */
259 | setPressed(true);
260 | break;
261 |
262 | case MotionEvent.ACTION_MOVE:
263 | float x = event.getX();
264 | float y = event.getY();
265 | boolean isInside = (x > 0 && x < getWidth() && y > 0 && y < getHeight());
266 | if (isPressed() != isInside) {
267 | setPressed(isInside);
268 | }
269 | break;
270 |
271 | case MotionEvent.ACTION_UP:
272 | icon.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).setInterpolator(DECCELERATE_INTERPOLATOR);
273 | icon.animate().scaleX(1).scaleY(1).setInterpolator(DECCELERATE_INTERPOLATOR);
274 | if (isPressed()) {
275 | performClick();
276 | setPressed(false);
277 | }
278 | break;
279 | case MotionEvent.ACTION_CANCEL:
280 | setPressed(false);
281 | break;
282 | }
283 | return true;
284 | }
285 |
286 | /**
287 | * This drawable is shown when the button is a liked state.
288 | *
289 | * @param resId
290 | */
291 | public void setLikeDrawableRes(@DrawableRes int resId) {
292 | likeDrawable = ContextCompat.getDrawable(getContext(), resId);
293 |
294 | if (iconSize != 0) {
295 | likeDrawable = Utils.resizeDrawable(getContext(), likeDrawable, iconSize, iconSize);
296 | }
297 |
298 | if (isChecked) {
299 | icon.setImageDrawable(likeDrawable);
300 | }
301 | }
302 |
303 | /**
304 | * This drawable is shown when the button is in a liked state.
305 | *
306 | * @param likeDrawable
307 | */
308 | public void setLikeDrawable(Drawable likeDrawable) {
309 | this.likeDrawable = likeDrawable;
310 |
311 | if (iconSize != 0) {
312 | this.likeDrawable = Utils.resizeDrawable(getContext(), likeDrawable, iconSize, iconSize);
313 | }
314 |
315 | if (isChecked) {
316 | icon.setImageDrawable(this.likeDrawable);
317 | }
318 | }
319 |
320 | /**
321 | * This drawable will be shown when the button is in on unLiked state.
322 | *
323 | * @param resId
324 | */
325 | public void setUnlikeDrawableRes(@DrawableRes int resId) {
326 | unLikeDrawable = ContextCompat.getDrawable(getContext(), resId);
327 |
328 | if (iconSize != 0) {
329 | unLikeDrawable = Utils.resizeDrawable(getContext(), unLikeDrawable, iconSize, iconSize);
330 | }
331 |
332 | if (!isChecked) {
333 | icon.setImageDrawable(unLikeDrawable);
334 | }
335 | }
336 |
337 | /**
338 | * This drawable will be shown when the button is in on unLiked state.
339 | *
340 | * @param unLikeDrawable
341 | */
342 | public void setUnlikeDrawable(Drawable unLikeDrawable) {
343 | this.unLikeDrawable = unLikeDrawable;
344 |
345 | if (iconSize != 0) {
346 | this.unLikeDrawable = Utils.resizeDrawable(getContext(), unLikeDrawable, iconSize, iconSize);
347 | }
348 |
349 | if (!isChecked) {
350 | icon.setImageDrawable(this.unLikeDrawable);
351 | }
352 | }
353 |
354 | /**
355 | * Sets one of the three icons that are bundled with the library.
356 | *
357 | * @param currentIconType
358 | */
359 | public void setIcon(IconType currentIconType) {
360 | currentIcon = parseIconType(currentIconType);
361 | setLikeDrawableRes(currentIcon.getOnIconResourceId());
362 | setUnlikeDrawableRes(currentIcon.getOffIconResourceId());
363 | icon.setImageDrawable(this.unLikeDrawable);
364 | }
365 |
366 | public void setIcon() {
367 | setLikeDrawableRes(currentIcon.getOnIconResourceId());
368 | setUnlikeDrawableRes(currentIcon.getOffIconResourceId());
369 | icon.setImageDrawable(this.unLikeDrawable);
370 | }
371 |
372 | /**
373 | * Sets the size of the drawable/icon that's being used. The views that generate
374 | * the like effect are also updated to reflect the size of the icon.
375 | *
376 | * @param iconSize
377 | */
378 |
379 | public void setIconSizeDp(int iconSize) {
380 | setIconSizePx((int) Utils.dipToPixels(getContext(), (float) iconSize));
381 | }
382 |
383 | /**
384 | * Sets the size of the drawable/icon that's being used. The views that generate
385 | * the like effect are also updated to reflect the size of the icon.
386 | *
387 | * @param iconSize
388 | */
389 | public void setIconSizePx(int iconSize) {
390 | this.iconSize = iconSize;
391 | setEffectsViewSize();
392 | this.unLikeDrawable = Utils.resizeDrawable(getContext(), unLikeDrawable, iconSize, iconSize);
393 | this.likeDrawable = Utils.resizeDrawable(getContext(), likeDrawable, iconSize, iconSize);
394 | }
395 |
396 | /**
397 | * * Parses the specific icon based on string
398 | * version of its enum.
399 | * These icons are bundled with the library and
400 | * are accessed via objects that contain their
401 | * resource ids and an enum with their name.
402 | *
403 | * @param iconType
404 | * @return Icon
405 | */
406 | private Icon parseIconType(String iconType) {
407 | List icons = Utils.getIcons();
408 |
409 | for (Icon icon : icons) {
410 | if (icon.getIconType().name().toLowerCase().equals(iconType.toLowerCase())) {
411 | return icon;
412 | }
413 | }
414 |
415 | throw new IllegalArgumentException("Correct icon type not specified.");
416 | }
417 |
418 | /**
419 | * Parses the specific icon based on it's type.
420 | * These icons are bundled with the library and
421 | * are accessed via objects that contain their
422 | * resource ids and an enum with their name.
423 | *
424 | * @param iconType
425 | * @return
426 | */
427 | private Icon parseIconType(IconType iconType) {
428 | List icons = Utils.getIcons();
429 |
430 | for (Icon icon : icons) {
431 | if (icon.getIconType().equals(iconType)) {
432 | return icon;
433 | }
434 | }
435 |
436 | throw new IllegalArgumentException("Correct icon type not specified.");
437 | }
438 |
439 | /**
440 | * Listener that is triggered once the
441 | * button is in a liked or unLiked state
442 | *
443 | * @param likeListener
444 | */
445 | public void setOnLikeListener(OnLikeListener likeListener) {
446 | this.likeListener = likeListener;
447 | }
448 |
449 | /**
450 | * Listener that is triggered once the
451 | * button animation is completed
452 | *
453 | * @param animationEndListener
454 | */
455 | public void setOnAnimationEndListener(OnAnimationEndListener animationEndListener) {
456 | this.animationEndListener = animationEndListener;
457 | }
458 |
459 |
460 | /**
461 | * This set sets the colours that are used for the little dots
462 | * that will be exploding once the like button is clicked.
463 | *
464 | * @param primaryColor
465 | * @param secondaryColor
466 | */
467 | public void setExplodingDotColorsRes(@ColorRes int primaryColor, @ColorRes int secondaryColor) {
468 | dotsView.setColors(ContextCompat.getColor(getContext(), primaryColor), ContextCompat.getColor(getContext(), secondaryColor));
469 | }
470 |
471 | public void setExplodingDotColorsInt(@ColorInt int primaryColor, @ColorInt int secondaryColor) {
472 | dotsView.setColors(primaryColor, secondaryColor);
473 | }
474 |
475 | public void setCircleStartColorRes(@ColorRes int circleStartColor) {
476 | this.circleStartColor = ContextCompat.getColor(getContext(), circleStartColor);
477 | circleView.setStartColor(this.circleStartColor);
478 | }
479 |
480 | public void setCircleStartColorInt(@ColorInt int circleStartColor) {
481 | this.circleStartColor = circleStartColor;
482 | circleView.setStartColor(circleStartColor);
483 | }
484 |
485 | public void setCircleEndColorRes(@ColorRes int circleEndColor) {
486 | this.circleEndColor = ContextCompat.getColor(getContext(), circleEndColor);
487 | circleView.setEndColor(this.circleEndColor);
488 | }
489 |
490 | /**
491 | * This function updates the dots view and the circle
492 | * view with the respective sizes based on the size
493 | * of the icon being used.
494 | */
495 | private void setEffectsViewSize() {
496 | if (iconSize != 0) {
497 | dotsView.setSize((int) (iconSize * animationScaleFactor), (int) (iconSize * animationScaleFactor));
498 | circleView.setSize(iconSize, iconSize);
499 | }
500 | }
501 |
502 | /**
503 | * Sets the initial state of the button to liked
504 | * or unliked.
505 | *
506 | * @param status
507 | */
508 | public void setLiked(Boolean status) {
509 | if (status) {
510 | isChecked = true;
511 | icon.setImageDrawable(likeDrawable);
512 | } else {
513 | isChecked = false;
514 | icon.setImageDrawable(unLikeDrawable);
515 | }
516 | }
517 |
518 | /**
519 | * Returns current like state
520 | *
521 | * @return current like state
522 | */
523 | public boolean isLiked() {
524 | return isChecked;
525 | }
526 |
527 | @Override
528 | public void setEnabled(boolean enabled) {
529 | isEnabled = enabled;
530 | }
531 |
532 | /**
533 | * Sets the factor by which the dots should be sized.
534 | */
535 | public void setAnimationScaleFactor(float animationScaleFactor) {
536 | this.animationScaleFactor = animationScaleFactor;
537 |
538 | setEffectsViewSize();
539 | }
540 |
541 | }
542 |
--------------------------------------------------------------------------------
/likebutton/src/main/java/com/github/like/OnAnimationEndListener.java:
--------------------------------------------------------------------------------
1 | package com.github.like;
2 |
3 | public interface OnAnimationEndListener {
4 | void onAnimationEnd(LikeButton likeButton);
5 | }
6 |
--------------------------------------------------------------------------------
/likebutton/src/main/java/com/github/like/OnLikeListener.java:
--------------------------------------------------------------------------------
1 | package com.github.like;
2 |
3 | /**
4 | * Created by Joel on 23/12/2015.
5 | */
6 | public interface OnLikeListener {
7 | void liked(LikeButton likeButton);
8 | void unLiked(LikeButton likeButton);
9 | }
10 |
--------------------------------------------------------------------------------
/likebutton/src/main/java/com/github/like/Utils.java:
--------------------------------------------------------------------------------
1 | package com.github.like;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.graphics.Canvas;
7 | import android.graphics.drawable.BitmapDrawable;
8 | import android.graphics.drawable.Drawable;
9 | import android.graphics.drawable.VectorDrawable;
10 | import android.os.Build;
11 | import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
12 | import android.util.DisplayMetrics;
13 | import android.util.TypedValue;
14 |
15 | import com.github.like.view.R;
16 |
17 | import java.util.ArrayList;
18 | import java.util.List;
19 |
20 | /**
21 | * Created by Joel on 23/12/2015.
22 | */
23 | public class Utils {
24 | public static double mapValueFromRangeToRange(double value, double fromLow, double fromHigh, double toLow, double toHigh) {
25 | return toLow + ((value - fromLow) / (fromHigh - fromLow) * (toHigh - toLow));
26 | }
27 |
28 | public static double clamp(double value, double low, double high) {
29 | return Math.min(Math.max(value, low), high);
30 | }
31 |
32 | public static List getIcons() {
33 | List icons = new ArrayList<>();
34 | icons.add(new Icon(R.drawable.heart_on, R.drawable.heart_off, IconType.Heart));
35 | icons.add(new Icon(R.drawable.star_on, R.drawable.star_off, IconType.Star));
36 | icons.add(new Icon(R.drawable.thumb_on, R.drawable.thumb_off, IconType.Thumb));
37 |
38 | return icons;
39 | }
40 |
41 | public static Drawable resizeDrawable(Context context, Drawable drawable, int width, int height) {
42 | Bitmap bitmap = getBitmap(drawable, width, height);
43 | return new BitmapDrawable(context.getResources(), Bitmap.createScaledBitmap(bitmap, width, height, true));
44 | }
45 |
46 | public static Bitmap getBitmap(Drawable drawable, int width, int height) {
47 | if (drawable instanceof BitmapDrawable) {
48 | return ((BitmapDrawable) drawable).getBitmap();
49 | } else if (drawable instanceof VectorDrawableCompat) {
50 | return getBitmap((VectorDrawableCompat) drawable, width, height);
51 | } else if (drawable instanceof VectorDrawable) {
52 | return getBitmap((VectorDrawable) drawable, width, height);
53 | } else {
54 | throw new IllegalArgumentException("Unsupported drawable type");
55 | }
56 | }
57 |
58 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
59 | private static Bitmap getBitmap(VectorDrawable vectorDrawable, int width, int height) {
60 | Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
61 | Canvas canvas = new Canvas(bitmap);
62 | vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
63 | vectorDrawable.draw(canvas);
64 | return bitmap;
65 | }
66 |
67 | private static Bitmap getBitmap(VectorDrawableCompat vectorDrawable, int width, int height) {
68 | Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
69 | Canvas canvas = new Canvas(bitmap);
70 | vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
71 | vectorDrawable.draw(canvas);
72 | return bitmap;
73 | }
74 |
75 | public static float dipToPixels(Context context, float dipValue) {
76 | DisplayMetrics metrics = context.getResources().getDisplayMetrics();
77 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, metrics);
78 | }
79 | }
80 |
81 |
--------------------------------------------------------------------------------
/likebutton/src/main/res/drawable/heart_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/likebutton/src/main/res/drawable/heart_off.png
--------------------------------------------------------------------------------
/likebutton/src/main/res/drawable/heart_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/likebutton/src/main/res/drawable/heart_on.png
--------------------------------------------------------------------------------
/likebutton/src/main/res/drawable/star_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/likebutton/src/main/res/drawable/star_off.png
--------------------------------------------------------------------------------
/likebutton/src/main/res/drawable/star_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/likebutton/src/main/res/drawable/star_on.png
--------------------------------------------------------------------------------
/likebutton/src/main/res/drawable/thumb_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/likebutton/src/main/res/drawable/thumb_off.png
--------------------------------------------------------------------------------
/likebutton/src/main/res/drawable/thumb_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/likebutton/src/main/res/drawable/thumb_on.png
--------------------------------------------------------------------------------
/likebutton/src/main/res/layout/likeview.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
17 |
18 |
24 |
25 |
--------------------------------------------------------------------------------
/likebutton/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/likebutton/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | LikeView
3 |
4 |
--------------------------------------------------------------------------------
/screenshot/1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/screenshot/1.gif
--------------------------------------------------------------------------------
/screenshot/2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/screenshot/2.gif
--------------------------------------------------------------------------------
/screenshot/3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/screenshot/3.gif
--------------------------------------------------------------------------------
/screenshot/4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/screenshot/4.gif
--------------------------------------------------------------------------------
/screenshot/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leiyun-studio/TouYin_DemoClient/c28fee637d9ce7ee869af6d8bfc453fc80d6ffce/screenshot/5.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':likebutton'
2 |
--------------------------------------------------------------------------------