├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── ic_star.png │ │ │ │ ├── singapore.jpg │ │ │ │ ├── playing_com_into.png │ │ │ │ ├── shape_white.xml │ │ │ │ ├── shape_progressbar_bg.xml │ │ │ │ ├── shape_progressbar_progress.xml │ │ │ │ ├── info_background.xml │ │ │ │ ├── layer_list_progress_drawable.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── red.png │ │ │ │ ├── share.webp │ │ │ │ ├── avatar.webp │ │ │ │ ├── img_egg.jpg │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── personal_bg.webp │ │ │ │ ├── ui_scale_img.jpg │ │ │ │ ├── ui_scale_img_2.jpg │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── dimen.xml │ │ │ │ ├── styles.xml │ │ │ │ ├── attrs.xml │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── material_colors.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── view_pager.xml │ │ │ │ ├── fragment_list.xml │ │ │ │ ├── demo_main_holder.xml │ │ │ │ ├── scene_scale_image_view.xml │ │ │ │ ├── fragment_empty.xml │ │ │ │ ├── scene_particle_scattering.xml │ │ │ │ ├── scene_scale_alpha.xml │ │ │ │ ├── activity_memory_lead.xml │ │ │ │ ├── activity_main.xml │ │ │ │ ├── scene_wave_view.xml │ │ │ │ ├── view_douban_bottom_content.xml │ │ │ │ ├── scene_ellipsized_text.xml │ │ │ │ ├── scene_sport_view.xml │ │ │ │ ├── holder_bootom_sheet.xml │ │ │ │ ├── scene_path.xml │ │ │ │ ├── relatelayout_gone_test.xml │ │ │ │ ├── radio_demo.xml │ │ │ │ ├── scene_flip_bord_view.xml │ │ │ │ ├── scene_linear_layout_test.xml │ │ │ │ ├── layout_barrier_demo.xml │ │ │ │ ├── chin_demo.xml │ │ │ │ ├── scene_constraint_layout_test.xml │ │ │ │ ├── scene_music_album_detail.xml │ │ │ │ ├── constrain_layout_demo.xml │ │ │ │ ├── scene_viewpager_test.xml │ │ │ │ ├── relatelayout_ui_test.xml │ │ │ │ ├── constrain_compare_layout.xml │ │ │ │ ├── lineralayout_ui_test.xml │ │ │ │ ├── view_douban_toolbar.xml │ │ │ │ └── google_contrain_demo.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── starts │ │ │ │ └── hencoderview │ │ │ │ ├── remote │ │ │ │ └── ApiService.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── scene │ │ │ │ ├── ViewBindingScene.kt │ │ │ │ ├── TagLayoutScene.kt │ │ │ │ ├── ScaleImageScene.kt │ │ │ │ ├── CustomLayoutScene.kt │ │ │ │ ├── ParticleScatteringScene.kt │ │ │ │ ├── ViewGroupScene.kt │ │ │ │ ├── LinearTestScene.kt │ │ │ │ ├── TransitionScene.kt │ │ │ │ ├── ConstrainTestScene.kt │ │ │ │ ├── CustomUIScene.kt │ │ │ │ ├── ConstrainCompareScene.kt │ │ │ │ ├── LinearLayoutScene.kt │ │ │ │ ├── RelateLayoutScene.kt │ │ │ │ ├── PathScene.kt │ │ │ │ ├── EllipsizedTextScene.kt │ │ │ │ ├── ScaleAlphaAnimScene.kt │ │ │ │ ├── SportScene.kt │ │ │ │ ├── FragmentTestScene.kt │ │ │ │ ├── WaveViewScene.kt │ │ │ │ ├── FloatRecyclerScene.kt │ │ │ │ └── FlipBoardScene.kt │ │ │ │ ├── view │ │ │ │ ├── PreViewOutLineProvider.kt │ │ │ │ ├── TestButton.kt │ │ │ │ ├── TestTextView.kt │ │ │ │ ├── TestFloatActionButton.kt │ │ │ │ ├── TestEditText.kt │ │ │ │ ├── TestImageView.kt │ │ │ │ ├── TestLinearLayout.kt │ │ │ │ ├── TestConstraintLayout.kt │ │ │ │ ├── ColoredTextView.java │ │ │ │ ├── TransitionLayout.kt │ │ │ │ ├── PathLayoutManager.kt │ │ │ │ ├── TestViewLayout.kt │ │ │ │ ├── TagLayout.kt │ │ │ │ ├── FlipBoardView.kt │ │ │ │ ├── SportView.kt │ │ │ │ ├── PathView.kt │ │ │ │ ├── CustomUITest.kt │ │ │ │ ├── CustomLayout.kt │ │ │ │ ├── ParticleScatteringView.kt │ │ │ │ └── ScaleImageView.kt │ │ │ │ ├── HenApplication.kt │ │ │ │ ├── ui │ │ │ │ ├── ListFragment.kt │ │ │ │ ├── MemoryLeakActivity.kt │ │ │ │ ├── ScalePageTransformer.java │ │ │ │ └── EmptyFragment.kt │ │ │ │ ├── util │ │ │ │ ├── Ext.kt │ │ │ │ └── QMUIAlignMiddleImageSpan.java │ │ │ │ └── container │ │ │ │ ├── BottomSheetLayout.kt │ │ │ │ └── CustomLayout.kt │ │ └── AndroidManifest.xml │ └── androidTest │ │ └── java │ │ └── com │ │ └── starts │ │ └── hencoderview │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── gradlew.bat ├── README.md └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='HenCoderView' 2 | include ':app' 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/drawable/ic_star.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-xhdpi/red.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/singapore.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/drawable/singapore.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/share.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-xhdpi/share.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/avatar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-xhdpi/avatar.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/img_egg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-xhdpi/img_egg.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/playing_com_into.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/drawable/playing_com_into.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/personal_bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-xhdpi/personal_bg.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ui_scale_img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-xhdpi/ui_scale_img.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ui_scale_img_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-xhdpi/ui_scale_img_2.jpg -------------------------------------------------------------------------------- /app/src/main/res/values/dimen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 56dp 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lori-zhao/HenCoderView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_progressbar_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_progressbar_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Apr 28 10:22:33 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-7.4-bin.zip 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/* 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | /projectFilesBackup 12 | /components/com/bana 13 | 14 | index.android.bundle 15 | *.log 16 | *.ipr 17 | *.iws 18 | apk/ 19 | *.apk 20 | -------------------------------------------------------------------------------- /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/layout/view_pager.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/info_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/remote/ApiService.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.remote 2 | 3 | import retrofit2.Call 4 | import retrofit2.http.GET 5 | import retrofit2.http.Path 6 | 7 | /** 8 | 9 | *文件描述:. 10 | *作者:Created by LostStars on 2020/11/17. 11 | *版本号:1.0 12 | 13 | */ 14 | interface ApiService { 15 | @GET ("users/{user}/repos") 16 | fun requestUserInfo(@Path("user") user:String):Call 17 | 18 | } -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview 2 | 3 | import com.bytedance.scene.Scene 4 | import com.bytedance.scene.ui.SceneActivity 5 | import com.starts.hencoderview.scene.MainScene 6 | 7 | class MainActivity : SceneActivity() { 8 | 9 | override fun supportRestore(): Boolean { 10 | return false 11 | } 12 | 13 | override fun getHomeSceneClass(): Class { 14 | return MainScene::class.java 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/layer_list_progress_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/demo_main_holder.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/scene_scale_image_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/ViewBindingScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.viewbinding.ViewBinding 8 | import com.bytedance.scene.ui.template.AppCompatScene 9 | 10 | class ViewBindingScene :AppCompatScene() { 11 | lateinit var binding:T 12 | override fun onCreateContentView(p0: LayoutInflater, p1: ViewGroup, p2: Bundle?): View { 13 | return binding.root 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/PreViewOutLineProvider.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.graphics.Outline 4 | import android.graphics.Rect 5 | import android.view.View 6 | import android.view.ViewOutlineProvider 7 | 8 | class PreViewOutLineProvider : ViewOutlineProvider() { 9 | override fun getOutline(view: View?, outline: Outline?) { 10 | val rect = Rect() 11 | view?.getGlobalVisibleRect(rect) 12 | val selfRect = Rect(0,0,rect.width(),rect.height()) 13 | outline?.setRoundRect(selfRect,20f) 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/HenApplication.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview 2 | 3 | import android.app.Application 4 | import timber.log.Timber 5 | import kotlin.properties.Delegates 6 | 7 | /** 8 | 9 | *文件描述:. 10 | *作者:Created by Administrator on 2020/11/10. 11 | *版本号:1.0 12 | 13 | */ 14 | class HenApplication : Application() { 15 | 16 | companion object{ 17 | var instance: HenApplication by Delegates.notNull() 18 | } 19 | 20 | override fun onCreate() { 21 | super.onCreate() 22 | instance = this 23 | Timber.plant(Timber.DebugTree()) 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/scene_particle_scattering.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/starts/hencoderview/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.starts.hencoderview", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /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/res/layout/scene_scale_alpha.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/TagLayoutScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.bytedance.scene.ui.template.AppCompatScene 8 | import com.starts.hencoderview.R 9 | 10 | /** 11 | 12 | *文件描述:. 13 | *作者:Created by Administrator on 2020/5/9. 14 | *版本号:1.0 15 | 16 | */ 17 | class TagLayoutScene : AppCompatScene() { 18 | override fun onCreateContentView( 19 | inflater: LayoutInflater, 20 | container: ViewGroup, 21 | savedInstanceState: Bundle? 22 | ): View? { 23 | return inflater.inflate(R.layout.scene_tag_layout,null) 24 | } 25 | 26 | override fun onActivityCreated(savedInstanceState: Bundle?) { 27 | super.onActivityCreated(savedInstanceState) 28 | setTitle("TagLayout") 29 | } 30 | 31 | 32 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_memory_lead.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/ScaleImageScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.bytedance.scene.ui.template.AppCompatScene 8 | import com.starts.hencoderview.R 9 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 10 | 11 | /** 12 | 13 | *文件描述:. 14 | *作者:Created by Administrator on 2020/5/20. 15 | *版本号:1.0 16 | 17 | */ 18 | class ScaleImageScene :AppCompatScene() { 19 | override fun onCreateContentView( 20 | inflater: LayoutInflater, 21 | container: ViewGroup, 22 | savedInstanceState: Bundle? 23 | ): View { 24 | return inflater.inflate(R.layout.scene_scale_image_view,null) 25 | } 26 | 27 | override fun onActivityCreated(savedInstanceState: Bundle?) { 28 | super.onActivityCreated(savedInstanceState) 29 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 30 | 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/scene_wave_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_douban_bottom_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/scene_ellipsized_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/scene_sport_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/holder_bootom_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 18 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | HenCoderView 3 | 在Android界面的展示中,我们常见工作流程是在xml写好布局文件,之后在Activity或者fragment中展示一个UI界面, 画面需要经 4 | 过xml解析,之后window要通过setView方法实际构造各个View对象,并且按照层级排列好,之后Android系统再找到合适 5 | 的时机,触发measure,layout,draw这一流程,但是随着布局嵌套的加深,xml的解析越来越复杂,测绘的次数会逐步提高,逐渐到 6 | 达失控的地步。那么,需要一种方式可以一次性解决这个问题,让我们的布局不再需要多次测量来确定自己的宽高,答案就是完全自己 7 | 接管ViewGroup的onMeasure,onLayout过程 8 | As the sun began to set over the horizon, Mary sat on the edge of the pier and dangled her feet over the water. She had come to this spot every evening for the past week in an attempt to clear her mind and find some peace. As the sound of waves lapping against the pier filled her ears, Mary closed her eyes and took a deep breath in, allowing the salty air to fill her lungs. For a moment, she felt truly content and at ease, the worries of her everyday life fading away into the background. As the last rays of sunlight disappeared over the water, Mary stood up from the pier and walked back towards her car, feeling refreshed and ready to take on whatever challenges the next day may bring. 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/CustomLayoutScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.bytedance.scene.ui.template.AppCompatScene 8 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 9 | import com.starts.hencoderview.view.TestViewLayout 10 | import com.starts.hencoderview.view.matchParent 11 | import com.starts.hencoderview.view.wrapContent 12 | 13 | class CustomLayoutScene : AppCompatScene() { 14 | private lateinit var rootView: TestViewLayout 15 | override fun onCreateContentView(p0: LayoutInflater, p1: ViewGroup, p2: Bundle?): View { 16 | rootView = TestViewLayout(p1.context) 17 | return rootView 18 | } 19 | 20 | override fun onActivityCreated(savedInstanceState: Bundle?) { 21 | super.onActivityCreated(savedInstanceState) 22 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 23 | setToolbarVisible(true) 24 | rootView.leftTv.postDelayed({ 25 | rootView.leftTv.text = 26 | "我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字" 27 | }, 4000) 28 | } 29 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/ParticleScatteringScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.graphics.BitmapFactory 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import com.bytedance.scene.ui.template.AppCompatScene 9 | import com.starts.hencoderview.databinding.SceneParticleScatteringBinding 10 | import com.starts.hencoderview.databinding.SceneViewpagerTestBinding 11 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 12 | 13 | /** 14 | 15 | *文件描述:. 16 | *作者:Created by Administrator on 2020/10/23. 17 | *版本号:1.0 18 | 19 | */ 20 | class ParticleScatteringScene: AppCompatScene() { 21 | 22 | lateinit var binding: SceneParticleScatteringBinding 23 | 24 | override fun onCreateContentView( 25 | inflater: LayoutInflater, 26 | container: ViewGroup, 27 | savedInstanceState: Bundle? 28 | ): View { 29 | binding = SceneParticleScatteringBinding.inflate(inflater) 30 | return binding.root 31 | } 32 | 33 | override fun onActivityCreated(savedInstanceState: Bundle?) { 34 | super.onActivityCreated(savedInstanceState) 35 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 36 | 37 | 38 | 39 | } 40 | 41 | 42 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/ViewGroupScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.graphics.drawable.ColorDrawable 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.view.ViewGroup.LayoutParams.MATCH_PARENT 9 | import com.bytedance.scene.ui.template.AppCompatScene 10 | import com.starts.hencoderview.util.getMaterialColor 11 | 12 | /** 13 | 14 | *文件描述:. 15 | *作者:Created by zhaosibo on 2021/3/24. 16 | *版本号:1.0 17 | 18 | */ 19 | class ViewGroupScene : AppCompatScene() { 20 | 21 | 22 | override fun onCreateContentView( 23 | inflater: LayoutInflater, 24 | container: ViewGroup, 25 | savedInstanceState: Bundle? 26 | ): View { 27 | val root = View(container.context).apply { 28 | layoutParams = ViewGroup.LayoutParams(MATCH_PARENT,MATCH_PARENT) 29 | background = ColorDrawable(getMaterialColor(resources , 1)) 30 | } 31 | return root 32 | } 33 | 34 | 35 | 36 | override fun onActivityCreated(savedInstanceState: Bundle?) { 37 | super.onActivityCreated(savedInstanceState) 38 | setStatusBarVisible(true) 39 | setToolbarVisible(true) 40 | setTitle("ViewGroupScene") 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/LinearTestScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Build 5 | import android.os.Bundle 6 | import android.os.Handler 7 | import android.os.Looper 8 | import android.view.* 9 | import android.widget.Toast 10 | import androidx.annotation.RequiresApi 11 | import com.bytedance.scene.ui.template.AppCompatScene 12 | import com.starts.hencoderview.databinding.SceneLinearLayoutTestBinding 13 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 14 | import timber.log.Timber 15 | 16 | class LinearTestScene : AppCompatScene() { 17 | lateinit var binding: SceneLinearLayoutTestBinding 18 | @SuppressLint("SetTextI18n") 19 | override fun onCreateContentView(p0: LayoutInflater, p1: ViewGroup, p2: Bundle?): View { 20 | binding = SceneLinearLayoutTestBinding.inflate(p0) 21 | return binding.root 22 | } 23 | 24 | @SuppressLint("SetTextI18n") 25 | override fun onActivityCreated(savedInstanceState: Bundle?) { 26 | super.onActivityCreated(savedInstanceState) 27 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 28 | binding.tvShort.text = "我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字," 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/TestButton.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Paint 6 | import android.util.AttributeSet 7 | import androidx.appcompat.widget.AppCompatButton 8 | import androidx.core.content.ContextCompat 9 | import com.starts.hencoderview.R 10 | import timber.log.Timber 11 | 12 | class TestButton:AppCompatButton { 13 | 14 | val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 15 | color = ContextCompat.getColor(context, R.color.pink_300) 16 | textSize = 32f 17 | } 18 | var measureCount = 0 19 | 20 | constructor(context: Context) : super(context) 21 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 22 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( 23 | context, 24 | attrs, 25 | defStyleAttr 26 | ) 27 | 28 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 29 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 30 | Timber.tag("onMeasure").d("measureCount = ${measureCount++} hashcode = ${this.hashCode()}") 31 | } 32 | 33 | override fun onDraw(canvas: Canvas) { 34 | super.onDraw(canvas) 35 | canvas.drawText("c=$measureCount" , 40f,40f,paint) 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/TestTextView.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Color 6 | import android.graphics.Paint 7 | import android.util.AttributeSet 8 | import androidx.core.content.ContextCompat 9 | import com.starts.hencoderview.R 10 | import timber.log.Timber 11 | 12 | class TestTextView :androidx.appcompat.widget.AppCompatTextView { 13 | val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 14 | color = ContextCompat.getColor(context, R.color.pink_300) 15 | textSize = 32f 16 | } 17 | var measureCount = 0 18 | constructor(context: Context) : super(context) 19 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 20 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( 21 | context, 22 | attrs, 23 | defStyleAttr 24 | ) 25 | 26 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 27 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 28 | Timber.tag("onMeasure").d("measureCount = ${measureCount++} hashcode = ${this.hashCode()}") 29 | } 30 | 31 | override fun onDraw(canvas: Canvas) { 32 | super.onDraw(canvas) 33 | canvas.drawText("c=$measureCount" , 40f,40f,paint) 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1565c0 4 | #0d47a1 5 | #42a5f5 6 | #ffffffff 7 | 8 | #f36c60 9 | #aed581 10 | #4db6ac 11 | #f06292 12 | #a1887f 13 | #ba68c8 14 | #dce775 15 | #90a4ae 16 | #9575cd 17 | #42bd41 18 | #7986cb 19 | #fff176 20 | #91a7ff 21 | #ffb74d 22 | #4fc3f7 23 | #ff8a65 24 | #4dd0e1 25 | #e0e0e0 26 | #78909C 27 | 28 | #99000000 29 | #fafafa 30 | 31 | #4D97FF 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/ui/ListFragment.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.ui 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.recyclerview.widget.LinearLayoutManager 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.starts.hencoderview.scene.InnerAdapter 11 | 12 | /** 13 | 14 | *文件描述:. 15 | *作者:Created by lorizhao on 2022/3/2. 16 | *版本号:1.0 17 | 18 | */ 19 | class ListFragment:Fragment() { 20 | override fun onCreateView( 21 | inflater: LayoutInflater, 22 | container: ViewGroup?, 23 | savedInstanceState: Bundle? 24 | ): View { 25 | val binding = RecyclerView(requireContext()).apply { 26 | this.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT) 27 | } 28 | val data = arrayListOf("我是第一个","12323124","48999584","239863436", 29 | "12323124","48999584","239863436","12323124","48999584","239863436","12323124", 30 | "48999584","239863436","12323124","48999584","239863436","12323124", 31 | "48999584","我是最后一个") 32 | binding.adapter = InnerAdapter(data) 33 | binding.layoutManager = LinearLayoutManager(requireContext()) 34 | return binding 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/TestFloatActionButton.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Paint 6 | import android.util.AttributeSet 7 | import androidx.core.content.ContextCompat 8 | import com.starts.hencoderview.R 9 | import timber.log.Timber 10 | 11 | class TestFloatActionButton : com.google.android.material.floatingactionbutton.FloatingActionButton { 12 | 13 | val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 14 | color = ContextCompat.getColor(context, R.color.pink_300) 15 | textSize = 32f 16 | } 17 | var measureCount = 0 18 | constructor(context: Context) : super(context) 19 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 20 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( 21 | context, 22 | attrs, 23 | defStyleAttr 24 | ) 25 | 26 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 27 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 28 | Timber.tag("onMeasure").d("measureCount = ${measureCount++} hashcode = ${this.hashCode()}") 29 | } 30 | 31 | override fun onDraw(canvas: Canvas) { 32 | super.onDraw(canvas) 33 | canvas.drawText("c=$measureCount" , 40f,40f,paint) 34 | } 35 | 36 | 37 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/TestEditText.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Paint 6 | import android.util.AttributeSet 7 | import android.widget.EditText 8 | import androidx.core.content.ContextCompat 9 | import com.starts.hencoderview.R 10 | import timber.log.Timber 11 | 12 | class TestEditText : androidx.appcompat.widget.AppCompatEditText { 13 | 14 | 15 | val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 16 | color = ContextCompat.getColor(context, R.color.pink_300) 17 | textSize = 32f 18 | } 19 | var measureCount = 0 20 | 21 | constructor(context: Context) : super(context) 22 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 23 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( 24 | context, 25 | attrs, 26 | defStyleAttr 27 | ) 28 | 29 | 30 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 31 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 32 | Timber.tag("onMeasure").d("measureCount = ${measureCount++} hashcode = ${this.hashCode()}") 33 | } 34 | 35 | override fun onDraw(canvas: Canvas) { 36 | super.onDraw(canvas) 37 | canvas.drawText("c=$measureCount" , 40f,40f,paint) 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/scene_path.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 11 | 22 | 23 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/TestImageView.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Color 6 | import android.graphics.Paint 7 | import android.util.AttributeSet 8 | import androidx.appcompat.widget.AppCompatImageView 9 | import androidx.core.content.ContextCompat 10 | import com.starts.hencoderview.R 11 | import timber.log.Timber 12 | 13 | class TestImageView: AppCompatImageView{ 14 | 15 | val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 16 | color = ContextCompat.getColor(context, R.color.pink_300) 17 | textSize = 32f 18 | } 19 | var measureCount = 0 20 | 21 | constructor(context: Context) : super(context) 22 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 23 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( 24 | context, 25 | attrs, 26 | defStyleAttr 27 | ) 28 | 29 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 30 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 31 | Timber.tag("onMeasure").d("measureCount = ${measureCount++} hashcode = ${this.hashCode()}") 32 | } 33 | 34 | override fun onDraw(canvas: Canvas) { 35 | super.onDraw(canvas) 36 | canvas.drawText("c=$measureCount" , 40f,40f,paint) 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/relatelayout_gone_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 22 | 23 | 24 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/ui/MemoryLeakActivity.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.ui 2 | 3 | import android.os.Bundle 4 | import android.os.Handler 5 | import android.os.Looper 6 | import android.widget.TextView 7 | import android.widget.Toast 8 | import androidx.appcompat.app.AppCompatActivity 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.starts.hencoderview.R 11 | import com.starts.hencoderview.remote.ApiService 12 | import leakcanary.LeakCanary 13 | import okhttp3.OkHttpClient 14 | import okhttp3.Request 15 | import retrofit2.Call 16 | import retrofit2.Callback 17 | import retrofit2.Response 18 | import retrofit2.Retrofit 19 | import java.io.IOException 20 | import kotlin.concurrent.thread 21 | 22 | /** 23 | 24 | *文件描述:. 25 | *作者:Created by Administrator on 2020/11/13. 26 | *版本号:1.0 27 | 28 | */ 29 | class MemoryLeakActivity : AppCompatActivity() { 30 | 31 | lateinit var tvPost: TextView 32 | 33 | private val handler = Handler{ 34 | tvPost.text = "收到" 35 | true 36 | } 37 | 38 | override fun onCreate(savedInstanceState: Bundle?) { 39 | super.onCreate(savedInstanceState) 40 | setContentView(R.layout.activity_memory_lead) 41 | 42 | tvPost = findViewById(R.id.tvPost) 43 | tvPost.setOnClickListener { 44 | handler.postDelayed({ 45 | Toast.makeText(this,"开始执行" ,Toast.LENGTH_LONG).show() 46 | },15000) 47 | } 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/TransitionScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.transition.Scene 8 | import androidx.transition.TransitionManager 9 | import com.bytedance.scene.ui.template.AppCompatScene 10 | import com.starts.hencoderview.container.CustomLayout 11 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 12 | import com.starts.hencoderview.util.getScreenWidth 13 | import com.starts.hencoderview.view.TransitionLayout 14 | 15 | class TransitionScene : AppCompatScene() { 16 | private lateinit var rootView : TransitionLayout 17 | override fun onCreateContentView( 18 | inflater: LayoutInflater, 19 | container: ViewGroup, 20 | savedInstanceState: Bundle? 21 | ): View { 22 | rootView = TransitionLayout(container.context) 23 | return rootView 24 | } 25 | 26 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 27 | super.onViewCreated(view, savedInstanceState) 28 | setToolbarVisible(true) 29 | setStatusBarVisible(true) 30 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 31 | rootView.button.setOnClickListener { 32 | val scene2 = Scene(rootView) 33 | val manager = TransitionManager() 34 | manager.transitionTo(scene2) 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/radio_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 26 | 27 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/values/material_colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @color/red_300 5 | @color/light_green_300 6 | @color/teal_300 7 | @color/pink_300 8 | @color/brown_300 9 | @color/purple_300 10 | @color/lime_300 11 | @color/blue_grey_300 12 | @color/deep_purple_300 13 | @color/green_300 14 | @color/indigo_300 15 | @color/yellow_300 16 | @color/blue_300 17 | @color/orange_300 18 | @color/light_blue_300 19 | @color/deep_orange_300 20 | @color/cyan_300 21 | @color/grey_300 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/ConstrainTestScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Build 5 | import android.os.Bundle 6 | import android.os.Handler 7 | import android.os.Looper 8 | import android.view.FrameMetrics 9 | import android.view.LayoutInflater 10 | import android.view.View 11 | import android.view.ViewGroup 12 | import android.widget.Toast 13 | import androidx.annotation.RequiresApi 14 | import com.bytedance.scene.ui.template.AppCompatScene 15 | import com.starts.hencoderview.databinding.SceneConstraintLayoutTestBinding 16 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 17 | import timber.log.Timber 18 | 19 | class ConstrainTestScene : AppCompatScene() { 20 | lateinit var binding: SceneConstraintLayoutTestBinding 21 | @SuppressLint("SetTextI18n") 22 | @RequiresApi(Build.VERSION_CODES.N) 23 | override fun onCreateContentView(p0: LayoutInflater, p1: ViewGroup, p2: Bundle?): View { 24 | binding = SceneConstraintLayoutTestBinding.inflate(p0) 25 | return binding.root 26 | } 27 | 28 | @SuppressLint("SetTextI18n") 29 | override fun onActivityCreated(savedInstanceState: Bundle?) { 30 | super.onActivityCreated(savedInstanceState) 31 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 32 | binding.tvShort.postDelayed({binding.tvShort.text = "我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,我是长文字,"},4000) 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/TestLinearLayout.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Color 6 | import android.graphics.Paint 7 | import android.util.AttributeSet 8 | import android.widget.LinearLayout 9 | import timber.log.Timber 10 | 11 | class TestLinearLayout :LinearLayout { 12 | init { 13 | setWillNotDraw(false) 14 | } 15 | val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 16 | color = Color.BLACK 17 | textSize = 32f 18 | } 19 | private var measureCount = 0 20 | constructor(context: Context?) : super(context) 21 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 22 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( 23 | context, 24 | attrs, 25 | defStyleAttr 26 | ) 27 | 28 | constructor( 29 | context: Context?, 30 | attrs: AttributeSet?, 31 | defStyleAttr: Int, 32 | defStyleRes: Int 33 | ) : super(context, attrs, defStyleAttr, defStyleRes) 34 | 35 | 36 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 37 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 38 | Timber.tag("onMeasure").d("measureCount = ${measureCount++} hashcode = ${this.hashCode()}") 39 | } 40 | 41 | override fun onDraw(canvas: Canvas) { 42 | super.onDraw(canvas) 43 | canvas.drawText("measureCount = $measureCount" , 40f,40f,paint) 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/CustomUIScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.view.ViewTreeObserver 8 | import com.bytedance.scene.ui.template.AppCompatScene 9 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 10 | import com.starts.hencoderview.view.CustomUITest 11 | import timber.log.Timber 12 | 13 | class CustomUIScene : AppCompatScene() { 14 | lateinit var root: CustomUITest 15 | var firstDate = 0L 16 | var secondDate = 0L 17 | val preDraw = ViewTreeObserver.OnPreDrawListener { 18 | secondDate = System.currentTimeMillis() 19 | val result = secondDate - firstDate 20 | firstDate = secondDate 21 | Timber.tag("preDraw").d("result = $result") 22 | true 23 | } 24 | override fun onCreateContentView(p0: LayoutInflater, p1: ViewGroup, p2: Bundle?): View { 25 | firstDate = System.currentTimeMillis() 26 | root = CustomUITest(context = p1.context) 27 | root.viewTreeObserver.addOnPreDrawListener(preDraw) 28 | return root 29 | } 30 | 31 | override fun onActivityCreated(savedInstanceState: Bundle?) { 32 | super.onActivityCreated(savedInstanceState) 33 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 34 | } 35 | 36 | 37 | override fun onDestroyView() { 38 | root.viewTreeObserver.removeOnPreDrawListener(preDraw) 39 | super.onDestroyView() 40 | 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/ConstrainCompareScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.view.ViewTreeObserver 8 | import com.bytedance.scene.ui.template.AppCompatScene 9 | import com.starts.hencoderview.databinding.ConstrainCompareLayoutBinding 10 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 11 | import timber.log.Timber 12 | 13 | class ConstrainCompareScene : AppCompatScene() { 14 | var firstDate = 0L 15 | var secondDate = 0L 16 | val preDraw = ViewTreeObserver.OnPreDrawListener { 17 | secondDate = System.currentTimeMillis() 18 | val result = secondDate - firstDate 19 | firstDate = secondDate 20 | Timber.tag("preDraw").d("result = $result") 21 | true 22 | } 23 | lateinit var binding: ConstrainCompareLayoutBinding 24 | override fun onCreateContentView(p0: LayoutInflater, p1: ViewGroup, p2: Bundle?): View { 25 | firstDate = System.currentTimeMillis() 26 | binding = ConstrainCompareLayoutBinding.inflate(p0) 27 | binding.root.viewTreeObserver.addOnPreDrawListener(preDraw) 28 | return binding.root 29 | } 30 | 31 | override fun onActivityCreated(savedInstanceState: Bundle?) { 32 | super.onActivityCreated(savedInstanceState) 33 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 34 | } 35 | override fun onDestroyView() { 36 | binding.root.viewTreeObserver.removeOnPreDrawListener(preDraw) 37 | super.onDestroyView() 38 | 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/LinearLayoutScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.view.ViewTreeObserver 8 | import com.bytedance.scene.ui.template.AppCompatScene 9 | import com.starts.hencoderview.databinding.LineralayoutUiTestBinding 10 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 11 | import timber.log.Timber 12 | 13 | class LinearLayoutScene : AppCompatScene() { 14 | lateinit var binding:LineralayoutUiTestBinding 15 | 16 | var firstDate = 0L 17 | var secondDate = 0L 18 | val preDraw = ViewTreeObserver.OnPreDrawListener { 19 | secondDate = System.currentTimeMillis() 20 | val result = secondDate - firstDate 21 | firstDate = secondDate 22 | Timber.tag("preDraw").d("result = $result") 23 | true 24 | } 25 | 26 | override fun onCreateContentView(p0: LayoutInflater, p1: ViewGroup, p2: Bundle?): View { 27 | firstDate = System.currentTimeMillis() 28 | binding = LineralayoutUiTestBinding.inflate(p0) 29 | binding.root.viewTreeObserver.addOnPreDrawListener(preDraw) 30 | return binding.root 31 | } 32 | 33 | override fun onActivityCreated(savedInstanceState: Bundle?) { 34 | super.onActivityCreated(savedInstanceState) 35 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 36 | } 37 | 38 | 39 | override fun onDestroyView() { 40 | binding.root.viewTreeObserver.removeOnPreDrawListener(preDraw) 41 | super.onDestroyView() 42 | 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/RelateLayoutScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.view.ViewTreeObserver 8 | import com.bytedance.scene.ui.template.AppCompatScene 9 | import com.starts.hencoderview.databinding.RelatelayoutUiTestBinding 10 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 11 | import timber.log.Timber 12 | 13 | class RelateLayoutScene : AppCompatScene() { 14 | lateinit var binding : RelatelayoutUiTestBinding 15 | var firstDate = 0L 16 | var secondDate = 0L 17 | val preDraw = ViewTreeObserver.OnPreDrawListener { 18 | secondDate = System.currentTimeMillis() 19 | val result = secondDate - firstDate 20 | firstDate = secondDate 21 | Timber.tag("preDraw").d("result = $result") 22 | true 23 | } 24 | 25 | 26 | override fun onCreateContentView(p0: LayoutInflater, p1: ViewGroup, p2: Bundle?): View { 27 | firstDate = System.currentTimeMillis() 28 | binding = RelatelayoutUiTestBinding.inflate(p0) 29 | binding.root.viewTreeObserver.addOnPreDrawListener(preDraw) 30 | return binding.root 31 | } 32 | 33 | override fun onActivityCreated(savedInstanceState: Bundle?) { 34 | super.onActivityCreated(savedInstanceState) 35 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 36 | } 37 | 38 | 39 | override fun onDestroyView() { 40 | binding.root.viewTreeObserver.removeOnPreDrawListener(preDraw) 41 | super.onDestroyView() 42 | 43 | } 44 | 45 | 46 | 47 | 48 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/TestConstraintLayout.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Color 6 | import android.graphics.Paint 7 | import android.util.AttributeSet 8 | import androidx.constraintlayout.widget.ConstraintLayout 9 | import androidx.core.content.ContextCompat 10 | import com.starts.hencoderview.R 11 | import timber.log.Timber 12 | 13 | class TestConstraintLayout : ConstraintLayout { 14 | init { 15 | setWillNotDraw(false) 16 | } 17 | 18 | private var measureCount = 0 19 | val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 20 | color = ContextCompat.getColor(context, R.color.pink_300) 21 | textSize = 32f 22 | } 23 | 24 | constructor(context: Context) : super(context) 25 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 26 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( 27 | context, 28 | attrs, 29 | defStyleAttr 30 | ) 31 | 32 | constructor( 33 | context: Context, 34 | attrs: AttributeSet?, 35 | defStyleAttr: Int, 36 | defStyleRes: Int 37 | ) : super(context, attrs, defStyleAttr, defStyleRes) 38 | 39 | 40 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 41 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 42 | Timber.tag("onMeasure").d("measureCount = ${measureCount++} hashcode = ${this.hashCode()}") 43 | } 44 | 45 | override fun onDraw(canvas: Canvas) { 46 | super.onDraw(canvas) 47 | canvas.drawText("measureCount = $measureCount" , 40f,40f,paint) 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/scene_flip_bord_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 26 | 27 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/ColoredTextView.java: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.util.AttributeSet; 8 | 9 | import androidx.annotation.Nullable; 10 | 11 | import com.starts.hencoderview.util.ExtKt; 12 | 13 | import java.util.Random; 14 | 15 | public class ColoredTextView extends androidx.appcompat.widget.AppCompatTextView { 16 | private static final int[] COLORS = { 17 | Color.parseColor("#E91E63"), 18 | Color.parseColor("#673AB7"), 19 | Color.parseColor("#3F51B5"), 20 | Color.parseColor("#2196F3"), 21 | Color.parseColor("#009688"), 22 | Color.parseColor("#FF9800"), 23 | Color.parseColor("#FF5722"), 24 | Color.parseColor("#795548") 25 | }; 26 | private static final int[] TEXT_SIZES = { 27 | 16, 22, 28 28 | }; 29 | private static final Random random = new Random(); 30 | private static final int CORNER_RADIUS = ExtKt.dp2px(4); 31 | private static final int X_PADDING = ExtKt.dp2px(16); 32 | private static final int Y_PADDING = ExtKt.dp2px(8); 33 | 34 | Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 35 | 36 | public ColoredTextView(Context context, @Nullable AttributeSet attrs) { 37 | super(context, attrs); 38 | } 39 | 40 | { 41 | setTextColor(Color.WHITE); 42 | setTextSize(TEXT_SIZES[random.nextInt(3)]); 43 | paint.setColor(COLORS[random.nextInt(COLORS.length)]); 44 | setPadding(X_PADDING, Y_PADDING, X_PADDING, Y_PADDING); 45 | } 46 | 47 | @Override 48 | protected void onDraw(Canvas canvas) { 49 | canvas.drawRoundRect(0, 0, getWidth(), getHeight(), CORNER_RADIUS, CORNER_RADIUS, paint); 50 | super.onDraw(canvas); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/util/Ext.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.util 2 | 3 | import android.content.res.Resources 4 | import android.graphics.Color 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.starts.hencoderview.R 8 | 9 | /** 10 | 11 | *文件描述:. 12 | *作者:Created by Administrator on 2020/4/29. 13 | *版本号:1.0 14 | 15 | */ 16 | fun dp2px(dp: Int): Int { 17 | val density = Resources.getSystem().displayMetrics.density 18 | return (dp * density).toInt() 19 | } 20 | 21 | fun sp(sp: Int): Float { 22 | val scaledDensity = Resources.getSystem().displayMetrics.scaledDensity 23 | return sp * scaledDensity 24 | } 25 | 26 | fun getMaterialColor(resources: Resources, index: Int): Int { 27 | val colors = resources.obtainTypedArray(R.array.mdcolor_300); 28 | val returnColor = colors.getColor(index % colors.length(), Color.BLACK); 29 | colors.recycle(); 30 | return returnColor 31 | } 32 | 33 | fun getScreenWidth(): Int { 34 | return Resources.getSystem().displayMetrics.widthPixels 35 | } 36 | 37 | fun getScreenHeight(): Int { 38 | return Resources.getSystem().displayMetrics.heightPixels 39 | } 40 | 41 | fun View.isUnder(rawX: Float, rawY: Float): Boolean { 42 | val xy = IntArray(2) 43 | getLocationOnScreen(xy) 44 | return rawX.toInt() in xy[0]..(xy[0] + width) && rawY.toInt() in xy[1]..(xy[1] + height) 45 | } 46 | 47 | /** 48 | * 寻找 ViewGroup 中某个符合条件的子 View,支持递归遍历其子 View 49 | */ 50 | fun ViewGroup.findFirst(recursively: Boolean, predict: (View) -> Boolean): View? { 51 | for (i in 0 until childCount) { 52 | val v = getChildAt(i) 53 | if (predict(v)) { 54 | return v 55 | } 56 | if (recursively) { 57 | return (v as? ViewGroup)?.findFirst(recursively, predict) ?: continue 58 | } 59 | } 60 | return null 61 | } 62 | 63 | const val ARGUMENTS_STRING_TITLE = "ARGUMENTS_STRING_TITLE" 64 | -------------------------------------------------------------------------------- /app/src/main/res/layout/scene_linear_layout_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 26 | 27 | 33 | 34 | 35 | 36 | 37 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/TransitionLayout.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.animation.LayoutTransition 4 | import android.content.Context 5 | import android.view.Gravity 6 | import android.view.View 7 | import android.widget.Button 8 | import android.widget.TextView 9 | import androidx.core.content.ContextCompat 10 | import androidx.core.view.forEach 11 | import com.starts.hencoderview.R 12 | import com.starts.hencoderview.container.CustomLayout 13 | import com.starts.hencoderview.container.matchParent 14 | import com.starts.hencoderview.container.wrapContent 15 | import com.starts.hencoderview.util.dp2px 16 | import com.starts.hencoderview.util.getScreenWidth 17 | 18 | class TransitionLayout(context: Context) : CustomLayout(context) { 19 | val button = Button(context).apply { 20 | layoutParams = LayoutParams(dp2px(120), dp2px(40)) 21 | text ="增加" 22 | gravity = Gravity.CENTER 23 | } 24 | 25 | val tv1 = TextView(context).apply { 26 | layoutParams = LayoutParams(getScreenWidth()/2 , getScreenWidth()/2) 27 | setBackgroundColor(ContextCompat.getColor(context, R.color.blue_300)) 28 | text = "tv1" 29 | } 30 | 31 | val tv2 = TextView(context).apply { 32 | layoutParams = CustomLayout.LayoutParams(getScreenWidth() / 2, getScreenWidth() / 2) 33 | setBackgroundColor(ContextCompat.getColor(context, R.color.red_300)) 34 | text = "tv2" 35 | } 36 | 37 | init { 38 | this@TransitionLayout.addView(button) 39 | this@TransitionLayout.addView(tv1) 40 | 41 | } 42 | 43 | override fun onMeasureChildren(widthMeasureSpec: Int, heightMeasureSpec: Int): Dimension { 44 | forEachAutoMeasure() 45 | return Dimension(matchParent, matchParent) 46 | } 47 | 48 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { 49 | tv1.layout(0,0) 50 | tv2.layout(0, tv1.right) 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_barrier_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | 28 | 29 | 35 | 36 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/PathScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.SeekBar 8 | import com.bytedance.scene.ui.template.AppCompatScene 9 | import com.starts.hencoderview.databinding.ScenePathBinding 10 | 11 | /** 12 | 13 | *文件描述:. 14 | *作者:Created by LostStars on 2020/11/26. 15 | *版本号:1.0 16 | 17 | */ 18 | class PathScene : AppCompatScene() { 19 | lateinit var binding: ScenePathBinding 20 | 21 | override fun onCreateContentView( 22 | inflater: LayoutInflater, 23 | container: ViewGroup, 24 | savedInstanceState: Bundle? 25 | ): View? { 26 | binding = ScenePathBinding.inflate(inflater) 27 | return binding.root 28 | } 29 | 30 | 31 | override fun onActivityCreated(savedInstanceState: Bundle?) { 32 | super.onActivityCreated(savedInstanceState) 33 | setTitle("PathView") 34 | binding.seekBar1.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{ 35 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { 36 | 37 | } 38 | 39 | override fun onStartTrackingTouch(seekBar: SeekBar?) { 40 | } 41 | 42 | override fun onStopTrackingTouch(seekBar: SeekBar) { 43 | binding.pathView.startPercentage = seekBar.progress 44 | } 45 | 46 | }) 47 | 48 | binding.seekBar2.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{ 49 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { 50 | 51 | } 52 | 53 | override fun onStartTrackingTouch(seekBar: SeekBar?) { 54 | } 55 | 56 | override fun onStopTrackingTouch(seekBar: SeekBar) { 57 | binding.pathView.endPercentage = seekBar.progress 58 | } 59 | 60 | }) 61 | 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/EllipsizedTextScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.graphics.Color 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import com.bytedance.scene.ui.template.AppCompatScene 9 | import com.starts.hencoderview.R 10 | import com.starts.hencoderview.databinding.SceneEllipsizedTextBinding 11 | import com.starts.hencoderview.util.sp 12 | 13 | 14 | /** 15 | 16 | *文件描述:.实现 文字...文字,文字...icon,文字...文字+icon的样式 17 | * 当设置了isDisplay = true ,在Ellipsize = end 并且 多行的情况下,图片才会显示,并不会直接追加文本末尾 18 | * keepText不管在当前TextView是在缩略状态,keepText都会被保留 19 | * 非缩略状态下,会自动通过SpannableStringBuilder方案实现图文混排 20 | * 21 | * 作者:Created by Lorizhao on 2021/3/4. 22 | * 版本号:1.0 23 | * 24 | * 使用时,请使用 setTextWithKeepText 方法设置 保留图片和 保留文字 25 | * 单独修改文字的颜色和字体大小,并不会触发重绘,请提前修改,最后通过 setTextWithKeepText 方法触发 26 | * 可以单独设置保留文字的 大小,颜色,如果需要修改对齐方式,那就重写在OnDraw()方法中的绘制方法 27 | * 28 | * 在onMeasure()方法中判断文字是否会超出,并设置needOverDraw,触发在onDraw()方法中追加绘制图片和文字 29 | * 在onMeasure(),onDraw()方法中调用setText()方法后会再次出发onMeasure(),onDraw(),所以要注意结束条件,自测onMeasure(),onDraw()方法的调用次数 30 | * 作者:Created by lorizhao on 2021/2/8. 31 | * 版本号:1.0 32 | */ 33 | class EllipsizedTextScene : AppCompatScene() { 34 | lateinit var binding: SceneEllipsizedTextBinding 35 | override fun onCreateContentView( 36 | inflater: LayoutInflater, 37 | container: ViewGroup, 38 | savedInstanceState: Bundle? 39 | ): View { 40 | binding = SceneEllipsizedTextBinding.inflate(inflater) 41 | return binding.root 42 | } 43 | 44 | override fun onActivityCreated(savedInstanceState: Bundle?) { 45 | super.onActivityCreated(savedInstanceState) 46 | val text = 47 | "电影主题曲《my heart will go on 》,电影主题曲《my heart will go ," 48 | binding.tv1.text = text 49 | binding.tv1.setKeepTextSize(sp(12)) 50 | binding.tv1.setKeepTextColor( Color.GREEN) 51 | binding.tv1.setTextWithKeepText(text , "已经播放30%",false, R.drawable.playing_com_into, -1 , -1 ) 52 | binding.tv1.text = text 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/ui/ScalePageTransformer.java: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.ui; 2 | 3 | import android.view.View; 4 | 5 | import androidx.viewpager2.widget.ViewPager2; 6 | 7 | /** 8 | * @author Administrator 9 | */ 10 | 11 | public class ScalePageTransformer implements ViewPager2.PageTransformer { 12 | 13 | private static final float MIN_SCALE = 0.8f; 14 | private static final float MIN_ALPHA = 1f; 15 | 16 | /** 17 | * 当ViewPager滑动时,每一个界面都会回掉该方法,并且该方法跟随滑动不断回调 18 | * 参数: 19 | * view:当前停留显示的页面(注意:下面的循环不要直接用该view,因为该view会被自动加上一层FrameLayout布局) 20 | * position:当前页面滑动的位置,以当前页面的左侧边为0,两边终点分别为-1和1 21 | */ 22 | @Override 23 | public void transformPage(View view, float position) { 24 | int pageWidth = view.getWidth(); 25 | int pageHeight = view.getHeight(); 26 | // [-Infinity,-1) 27 | if (position < -1) { 28 | // This page is way off-screen to the left. 29 | view.setAlpha(0); 30 | 31 | } else if (position <= 1) { 32 | // a页滑动至b页 ; a页从 0.0 -1 ;b页从1 ~ 0.0 33 | // Modify the default slide transition to shrink the page as well 34 | float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position)); 35 | float vertMargin = pageHeight * (1 - scaleFactor) / 2; 36 | float horzMargin = pageWidth * (1 - scaleFactor) / 2; 37 | if (position < 0) { 38 | //api版本>=11,view相对原始位置的偏移量,不影响view属性 39 | view.setTranslationX(horzMargin - vertMargin / 2); 40 | } else { 41 | view.setTranslationX(-horzMargin + vertMargin / 2); 42 | } 43 | 44 | // Scale the page down (between MIN_SCALE and 1) 45 | view.setScaleX(scaleFactor); 46 | view.setScaleY(scaleFactor); 47 | 48 | // Fade the page relative to its size. 49 | view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE) 50 | / (1 - MIN_SCALE) * (1 - MIN_ALPHA)); 51 | 52 | } else { 53 | // This page is way off-screen to the right. 54 | view.setAlpha(0); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/res/layout/chin_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 23 | 24 | 36 | 37 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/ui/EmptyFragment.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.ui 2 | 3 | import android.graphics.Color 4 | import android.os.Bundle 5 | import android.util.Log 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import androidx.constraintlayout.widget.ConstraintLayout 10 | import androidx.fragment.app.Fragment 11 | import com.starts.hencoderview.R 12 | import com.starts.hencoderview.databinding.FragmentEmptyBinding 13 | import java.util.* 14 | import java.util.logging.Logger 15 | 16 | /** 17 | 18 | *文件描述:. 19 | *作者:Created by Administrator on 2020/7/16. 20 | *版本号:1.0 21 | 22 | */ 23 | class EmptyFragment : Fragment() { 24 | 25 | lateinit var binding:FragmentEmptyBinding 26 | 27 | override fun onCreateView( 28 | inflater: LayoutInflater, 29 | container: ViewGroup?, 30 | savedInstanceState: Bundle? 31 | ): View? { 32 | // return super.onCreateView(inflater, container, savedInstanceState) 33 | binding = FragmentEmptyBinding.inflate(inflater) 34 | return binding.root 35 | } 36 | 37 | 38 | @Deprecated("Deprecated in Java") 39 | override fun onActivityCreated(savedInstanceState: Bundle?) { 40 | super.onActivityCreated(savedInstanceState) 41 | val random = Random() 42 | val r = random.nextInt(256) 43 | val g = random.nextInt(256) 44 | val b = random.nextInt(256) 45 | binding.root.setBackgroundColor(Color.rgb(r, g, b)) 46 | binding.tvTag.text = hashCode().toString() 47 | } 48 | 49 | override fun onHiddenChanged(hidden: Boolean) { 50 | super.onHiddenChanged(hidden) 51 | Log.d("fragmentLife", "onHiddenChanged${hidden}") 52 | } 53 | 54 | @Deprecated("Deprecated in Java") 55 | override fun setUserVisibleHint(isVisibleToUser: Boolean) { 56 | super.setUserVisibleHint(isVisibleToUser) 57 | Log.d("fragmentLife", "setUserVisibleHint${isVisibleToUser}") 58 | } 59 | 60 | override fun onStart() { 61 | super.onStart() 62 | Log.d("fragmentLife", "hashCode:${this.hashCode()}:onStart") 63 | } 64 | 65 | override fun onResume() { 66 | super.onResume() 67 | Log.d("fragmentLife", "hashCode:${this.hashCode()}onResume") 68 | } 69 | 70 | 71 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/scene_constraint_layout_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 27 | 28 | 29 | 40 | 41 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/layout/scene_music_album_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 26 | 41 | 42 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/ScaleAlphaAnimScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.animation.Animator 4 | import android.animation.AnimatorSet 5 | import android.animation.ObjectAnimator 6 | import android.animation.ValueAnimator 7 | import android.os.Bundle 8 | import android.view.LayoutInflater 9 | import android.view.View 10 | import android.view.ViewGroup 11 | import android.view.animation.AnimationSet 12 | import android.view.animation.DecelerateInterpolator 13 | import android.view.animation.LinearInterpolator 14 | import com.bytedance.scene.ui.template.AppCompatScene 15 | import com.starts.hencoderview.databinding.SceneScaleAlphaBinding 16 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 17 | 18 | /** 19 | 20 | *文件描述:. 21 | *作者:Created by lorizhao on 2021/10/31. 22 | *版本号:1.0 23 | 24 | */ 25 | class ScaleAlphaAnimScene : AppCompatScene() { 26 | lateinit var binding: SceneScaleAlphaBinding 27 | override fun onCreateContentView( 28 | inflater: LayoutInflater, 29 | container: ViewGroup, 30 | savedInstanceState: Bundle? 31 | ): View { 32 | binding = SceneScaleAlphaBinding.inflate(inflater) 33 | return binding.root 34 | } 35 | 36 | override fun onActivityCreated(savedInstanceState: Bundle?) { 37 | super.onActivityCreated(savedInstanceState) 38 | setStatusBarVisible(true) 39 | setToolbarVisible(true) 40 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 41 | startScaleAnimation(binding.view) 42 | } 43 | 44 | private fun startScaleAnimation(view: View) { 45 | val scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.4f) 46 | val scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.4f) 47 | 48 | val alpha = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f) 49 | 50 | scaleX.repeatCount = ValueAnimator.INFINITE 51 | scaleY.repeatCount = ValueAnimator.INFINITE 52 | alpha.repeatCount = ValueAnimator.INFINITE 53 | 54 | scaleX.repeatMode = ValueAnimator.RESTART 55 | scaleY.repeatMode = ValueAnimator.RESTART 56 | alpha.repeatMode = ValueAnimator.RESTART 57 | val animatorSet = AnimatorSet() 58 | animatorSet.playTogether(scaleX,scaleY,alpha) 59 | animatorSet.interpolator = DecelerateInterpolator() 60 | animatorSet.duration = 800 61 | animatorSet.start() 62 | 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/PathLayoutManager.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.os.Looper 4 | import androidx.recyclerview.widget.LinearLayoutManager 5 | import androidx.recyclerview.widget.RecyclerView 6 | import java.util.logging.Handler 7 | 8 | /** 9 | 10 | *文件描述:. 11 | *作者:Created by Administrator on 2020/10/28. 12 | *版本号:1.0 13 | 14 | */ 15 | class PathLayoutManager : RecyclerView.LayoutManager() { 16 | override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams { 17 | return RecyclerView.LayoutParams( RecyclerView.LayoutParams.WRAP_CONTENT , RecyclerView.LayoutParams.WRAP_CONTENT) 18 | } 19 | 20 | override fun onMeasure( 21 | recycler: RecyclerView.Recycler, 22 | state: RecyclerView.State, 23 | widthSpec: Int, 24 | heightSpec: Int 25 | ) { 26 | super.onMeasure(recycler, state, widthSpec, heightSpec) 27 | } 28 | 29 | 30 | override fun isAutoMeasureEnabled(): Boolean { 31 | return super.isAutoMeasureEnabled() 32 | } 33 | 34 | override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) { 35 | super.onLayoutChildren(recycler, state) 36 | 37 | detachAndScrapAttachedViews(recycler) 38 | removeAndRecycleAllViews(recycler) 39 | 40 | } 41 | 42 | override fun canScrollHorizontally(): Boolean { 43 | return super.canScrollHorizontally() 44 | } 45 | 46 | override fun canScrollVertically(): Boolean { 47 | return super.canScrollVertically() 48 | } 49 | 50 | override fun scrollHorizontallyBy( 51 | dx: Int, 52 | recycler: RecyclerView.Recycler?, 53 | state: RecyclerView.State? 54 | ): Int { 55 | return super.scrollHorizontallyBy(dx, recycler, state) 56 | } 57 | 58 | override fun scrollVerticallyBy( 59 | dy: Int, 60 | recycler: RecyclerView.Recycler?, 61 | state: RecyclerView.State? 62 | ): Int { 63 | return super.scrollVerticallyBy(dy, recycler, state) 64 | } 65 | 66 | 67 | override fun scrollToPosition(position: Int) { 68 | super.scrollToPosition(position) 69 | } 70 | 71 | override fun smoothScrollToPosition( 72 | recyclerView: RecyclerView?, 73 | state: RecyclerView.State?, 74 | position: Int 75 | ) { 76 | super.smoothScrollToPosition(recyclerView, state, position) 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/res/layout/constrain_layout_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 21 | 22 | 32 | 33 | 38 | 39 | 44 | 45 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/res/layout/scene_viewpager_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 22 | 30 | 31 | 32 | 33 | 34 | 41 | 42 | 43 | 51 | 52 | 53 | 61 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/container/BottomSheetLayout.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.container 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.GradientDrawable 5 | import android.graphics.drawable.GradientDrawable.RECTANGLE 6 | import android.util.AttributeSet 7 | import android.view.View 8 | import androidx.core.content.ContextCompat 9 | import androidx.viewpager2.widget.ViewPager2 10 | import com.google.android.material.tabs.TabLayout 11 | import com.starts.hencoderview.R 12 | 13 | /** 14 | * 底部弹出式的 view 容器 15 | * @author lorizhao 16 | * @since 2022/3/22 17 | */ 18 | 19 | class BottomSheetLayout : CustomLayout { 20 | companion object{ 21 | const val TAG = "BottomSheetLayout" 22 | } 23 | 24 | /** 25 | * 拖拽的时候能展示的小白条 26 | * 不要用Gone ,引起重测,导致性能浪费,也会导致TabLayout位置升高 27 | */ 28 | val dragTip = View(context).apply { 29 | layoutParams = LayoutParams(40.dp , 4.dp) 30 | this@BottomSheetLayout.addView(this) 31 | this.background = GradientDrawable().apply { 32 | setColor(ContextCompat.getColor(context, R.color.cardview_dark_background)) 33 | shape = RECTANGLE 34 | val floatArray = floatArrayOf(20.dp * 1f,20.dp * 1f,20.dp * 1f,20.dp * 1f,20.dp * 1f,20.dp * 1f,20.dp * 1f,20.dp * 1f) 35 | cornerRadii = floatArray 36 | } 37 | } 38 | 39 | val bottomTabLayout = TabLayout(context).apply { 40 | layoutParams = LayoutParams(matchParent , 45.dp) 41 | this@BottomSheetLayout.addView(this) 42 | } 43 | val bottomViewPager = ViewPager2(context).apply { 44 | layoutParams = LayoutParams(matchParent , matchParent) 45 | overScrollMode = OVER_SCROLL_NEVER 46 | this@BottomSheetLayout.addView(this) 47 | } 48 | 49 | constructor(context: Context) : super(context) 50 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 51 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( 52 | context, 53 | attrs, 54 | defStyleAttr 55 | ) 56 | 57 | override fun onMeasureChildren(widthMeasureSpec: Int, heightMeasureSpec: Int):Dimension { 58 | dragTip.autoMeasure() 59 | bottomTabLayout.autoMeasure() 60 | bottomViewPager.measure(widthMeasureSpec,heightMeasureSpec - dragTip.measuredHeight - bottomTabLayout.measuredHeight) 61 | return Dimension(widthMeasureSpec,heightMeasureSpec) 62 | } 63 | 64 | /** 65 | * 布局TabLayout 和 ViewPager,还有拖拽的时候能展示的小白条 66 | */ 67 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { 68 | dragTip.layout(horizontalCenterX(dragTip) , 8.dp) 69 | bottomTabLayout.layout(0,dragTip.bottom + 20.dp) 70 | bottomViewPager.layout(0,dragTip.bottom + 20.dp) 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/TestViewLayout.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import android.util.AttributeSet 6 | import androidx.core.content.ContextCompat 7 | import androidx.core.view.setPadding 8 | import com.starts.hencoderview.R 9 | 10 | class TestViewLayout(context: Context) : CustomLayout(context) { 11 | 12 | private val topBg = TestTextView(context).apply { 13 | this.layoutParams = LayoutParams(matchParent, matchParent) 14 | setBackgroundColor(ContextCompat.getColor(context, R.color.blue_300)) 15 | } 16 | 17 | private val bottomBg = TestTextView(context).apply { 18 | this.layoutParams = LayoutParams(matchParent, matchParent) 19 | setBackgroundColor(ContextCompat.getColor(context, R.color.blue_grey_300)) 20 | } 21 | 22 | val leftTv = TestTextView(context).apply { 23 | this.layoutParams = LayoutParams(matchParent, wrapContent) 24 | setBackgroundColor(ContextCompat.getColor(context, R.color.red_300)) 25 | setPadding(20.dp) 26 | } 27 | 28 | private val rightTv = TestTextView(context).apply { 29 | this.layoutParams = LayoutParams(matchParent, wrapContent) 30 | setBackgroundColor(ContextCompat.getColor(context, R.color.orange_300)) 31 | setPadding(20.dp) 32 | } 33 | 34 | init { 35 | this@TestViewLayout.addView(topBg) 36 | this@TestViewLayout.addView(bottomBg) 37 | this@TestViewLayout.addView(leftTv) 38 | this@TestViewLayout.addView(rightTv) 39 | setBackgroundColor(Color.WHITE) 40 | } 41 | 42 | override fun onMeasureChildren(widthMeasureSpec: Int, heightMeasureSpec: Int): Dimension { 43 | topBg.measure( 44 | widthMeasureSpec, 45 | ((MeasureSpec.getSize(heightMeasureSpec) - 80.dp) / 2).toExactlyMeasureSpec() 46 | ) 47 | bottomBg.measure( 48 | widthMeasureSpec, 49 | ((MeasureSpec.getSize(heightMeasureSpec) - 80.dp) / 2).toExactlyMeasureSpec() 50 | ) 51 | leftTv.measure( 52 | (MeasureSpec.getSize(widthMeasureSpec) / 2).toExactlyMeasureSpec(), 53 | MeasureSpec.getSize(heightMeasureSpec).toAtMostMeasureSpec() 54 | ) 55 | rightTv.measure( 56 | (MeasureSpec.getSize(widthMeasureSpec) / 2).toExactlyMeasureSpec(), 57 | MeasureSpec.getSize(heightMeasureSpec).toAtMostMeasureSpec() 58 | ) 59 | 60 | return Dimension(widthMeasureSpec, heightMeasureSpec) 61 | } 62 | 63 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { 64 | topBg.layout(0, 40.dp) 65 | bottomBg.layout(0, topBg.bottom) 66 | leftTv.layout(0, topBg.top + topBg.verticalCenterTop(leftTv)) 67 | rightTv.layout(leftTv.right, topBg.top + topBg.verticalCenterTop(rightTv)) 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/TagLayout.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.graphics.Rect 6 | import android.util.AttributeSet 7 | import android.view.ViewGroup 8 | import kotlin.math.max 9 | 10 | 11 | /** 12 | 13 | *文件描述:. 14 | *作者:Created by Administrator on 2020/5/9. 15 | *版本号:1.0 16 | */ 17 | class TagLayout(context: Context?, attrs: AttributeSet?) : ViewGroup(context, attrs) { 18 | 19 | private val childBounds = ArrayList() 20 | //1.调用每个子 View 的 measure() 来计算子 View 的尺寸 21 | //2.计算子 View 的位置并保存子 View 的位置和尺寸 22 | //3.计算自己的尺寸并用 setMeasuredDimension() 保存 23 | @SuppressLint("DrawAllocation") 24 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 25 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 26 | 27 | val widthMeasureSize = MeasureSpec.getSize(widthMeasureSpec) 28 | // val widthMeasureMode = MeasureSpec.getMode(widthMeasureSpec) 29 | 30 | var widthUsed = 0 31 | var heightUsed = 0 32 | 33 | var maxHeight = 0 34 | var lineWidthUsed = 0 35 | 36 | for(i in 0 until childCount ){ 37 | val child = getChildAt(i) 38 | measureChildWithMargins(child,widthMeasureSpec, 0 , heightMeasureSpec ,heightUsed) 39 | if(lineWidthUsed + child.measuredWidth > widthMeasureSize){ 40 | heightUsed += maxHeight 41 | maxHeight = 0 42 | lineWidthUsed = 0 43 | measureChildWithMargins(child,widthMeasureSpec, 0 , heightMeasureSpec,heightUsed) 44 | } 45 | 46 | 47 | if(i < childBounds.size){ 48 | val bound = childBounds[i] 49 | bound.set(lineWidthUsed , heightUsed, lineWidthUsed + child.measuredWidth ,heightUsed + child.measuredHeight) 50 | }else{ 51 | val bound = Rect() 52 | bound.set(lineWidthUsed , heightUsed, lineWidthUsed + child.measuredWidth ,heightUsed + child.measuredHeight) 53 | childBounds.add(bound) 54 | } 55 | 56 | maxHeight = max(maxHeight , child.measuredHeight) 57 | 58 | lineWidthUsed += child.measuredWidth 59 | 60 | widthUsed = max(lineWidthUsed , widthUsed) 61 | } 62 | setMeasuredDimension(widthUsed , heightUsed+ maxHeight) 63 | } 64 | 65 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { 66 | for (i in 0 until childCount) { 67 | val bound = childBounds[i] 68 | val child = getChildAt(i) 69 | child.layout(bound.left, bound.top, bound.right, bound.bottom) 70 | } 71 | 72 | } 73 | 74 | override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams { 75 | return MarginLayoutParams(context, attrs) 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | //apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 32 7 | 8 | defaultConfig { 9 | applicationId "com.starts.hencoderview" 10 | minSdkVersion 22 11 | targetSdkVersion 32 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | sourceSets { 19 | main { 20 | java { 21 | java.srcDirs += 'src/main/kotlin' 22 | } 23 | } 24 | } 25 | 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | 31 | kotlinOptions { 32 | jvmTarget = JavaVersion.VERSION_1_8.toString() 33 | } 34 | 35 | viewBinding{ 36 | enabled = true 37 | } 38 | 39 | // dataBinding { 40 | // enabled true 41 | // } 42 | 43 | buildTypes { 44 | release { 45 | minifyEnabled false 46 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 47 | } 48 | } 49 | namespace 'com.starts.hencoderview' 50 | 51 | } 52 | ext{ 53 | scene_version = '1.3.1' 54 | } 55 | dependencies { 56 | implementation fileTree(dir: 'libs', include: ['*.jar']) 57 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 58 | implementation 'androidx.appcompat:appcompat:1.4.2' 59 | implementation 'androidx.core:core-ktx:1.7.0' 60 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3' 61 | implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' 62 | implementation 'com.google.android.material:material:1.5.0' 63 | testImplementation 'junit:junit:4.13.2' 64 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 65 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 66 | implementation 'com.jakewharton.timber:timber:5.0.1' 67 | 68 | implementation "com.github.bytedance.scene:scene:$scene_version" 69 | implementation "com.github.bytedance.scene:scene_navigation:$scene_version" 70 | implementation "com.github.bytedance.scene:scene_ui:$scene_version" 71 | implementation "com.github.bytedance.scene:scene_dialog:$scene_version" 72 | implementation "com.github.bytedance.scene:scene_shared_element_animation:$scene_version" 73 | implementation "com.github.bytedance.scene:scene_ktx:$scene_version" 74 | 75 | implementation 'com.github.zhpanvip:BannerViewPager:3.1.4' 76 | 77 | implementation 'com.squareup.retrofit2:retrofit:2.9.0' 78 | implementation 'com.squareup.okhttp3:okhttp:4.9.0' 79 | 80 | 81 | implementation 'com.github.bumptech.glide:glide:4.11.0' 82 | annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' 83 | debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/FlipBoardView.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.util.AttributeSet 6 | import android.view.View 7 | import com.starts.hencoderview.R 8 | import kotlin.math.sin 9 | 10 | /** 11 | 12 | *文件描述:. 13 | *作者:Created by Administrator on 2020/4/30. 14 | *版本号:1.0 15 | 16 | */ 17 | class FlipBoardView(context: Context, attrs: AttributeSet) : View(context, attrs) { 18 | 19 | private lateinit var bitmap: Bitmap 20 | lateinit var srcRect: Rect 21 | private lateinit var center: Point 22 | private val paint = Paint(Paint.ANTI_ALIAS_FLAG) 23 | private val camera = Camera() 24 | 25 | private var radius: Double = 0.00 26 | 27 | var angle: Float = 0f 28 | set(value) { 29 | field = value 30 | invalidate() 31 | } 32 | 33 | var cameraAngle: Float = 0f 34 | set(value) { 35 | field = value 36 | invalidate() 37 | } 38 | init { 39 | 40 | camera.setLocation(0f, 0f, -6f * context.resources.displayMetrics.density) 41 | } 42 | 43 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { 44 | super.onSizeChanged(w, h, oldw, oldh) 45 | center = Point(w / 2, h / 2) 46 | 47 | bitmap = BitmapFactory.decodeResource(context.resources, R.mipmap.red) 48 | srcRect = Rect(0, 0, bitmap.width, bitmap.height) 49 | radius = (bitmap.width) * sin(Math.toRadians(45.0)) 50 | } 51 | 52 | override fun onDraw(canvas: Canvas) { 53 | super.onDraw(canvas) 54 | // canvas.drawColor(ContextCompat.getColor(context, R.color.colorAccent)) 55 | canvas.drawBitmap(bitmap, 0f, 0f, paint) 56 | 57 | canvas.save() 58 | canvas.translate(center.x.toFloat(), center.y.toFloat()) 59 | canvas.rotate(angle) 60 | canvas.clipRect(-radius.toFloat(), -radius.toFloat(), radius.toFloat(), 0f) 61 | canvas.rotate(-angle) 62 | canvas.translate(-center.x.toFloat(), -center.y.toFloat()) 63 | canvas.drawBitmap( 64 | bitmap, 65 | center.x - bitmap.width / 2f, 66 | center.y - bitmap.height / 2f, 67 | paint 68 | ) 69 | canvas.restore() 70 | 71 | canvas.save() 72 | canvas.translate(center.x.toFloat(), center.y.toFloat()) 73 | canvas.rotate(angle) 74 | camera.save() 75 | camera.rotateX(cameraAngle) 76 | camera.applyToCanvas(canvas) 77 | camera.restore() 78 | canvas.clipRect(-radius.toFloat(), 0f, radius.toFloat(), radius.toFloat()) 79 | canvas.rotate(-angle) 80 | canvas.translate(-center.x.toFloat(), -center.y.toFloat()) 81 | canvas.drawBitmap( 82 | bitmap, 83 | center.x - bitmap.width / 2f, 84 | center.y - bitmap.height / 2f, 85 | paint 86 | ) 87 | canvas.restore() 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/relatelayout_ui_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 17 | 21 | 22 | 23 | 24 | 30 | 31 | 32 | 33 | 42 | 43 | 54 | 55 | 64 | 65 | 76 | 77 | -------------------------------------------------------------------------------- /app/src/main/res/layout/constrain_compare_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 26 | 27 | 34 | 35 | 45 | 46 | 54 | 55 | 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/SportScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.animation.Animator 4 | import android.animation.ObjectAnimator 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.view.animation.DecelerateInterpolator 10 | import android.widget.SeekBar 11 | import com.bytedance.scene.ui.template.AppCompatScene 12 | import com.starts.hencoderview.R 13 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 14 | import com.starts.hencoderview.view.SportView 15 | 16 | /** 17 | 18 | *文件描述:. 19 | *作者:Created by Administrator on 2020/4/29. 20 | *版本号:1.0 21 | 22 | */ 23 | class SportScene : AppCompatScene() { 24 | 25 | lateinit var sportView: SportView 26 | lateinit var seekBar: SeekBar 27 | 28 | override fun onCreateContentView( 29 | inflater: LayoutInflater, 30 | container: ViewGroup, 31 | savedInstanceState: Bundle? 32 | ): View { 33 | val view = inflater.inflate(R.layout.scene_sport_view, null, false) 34 | sportView = view.findViewById(R.id.sportView) 35 | seekBar = view.findViewById(R.id.seekBar) 36 | return view 37 | } 38 | 39 | override fun onActivityCreated(savedInstanceState: Bundle?) { 40 | super.onActivityCreated(savedInstanceState) 41 | setToolbarVisible(true) 42 | setStatusBarVisible(true) 43 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 44 | // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 45 | // requireActivity().window.decorView.systemUiVisibility = 46 | // View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 47 | // } 48 | val animator = ObjectAnimator.ofInt(sportView, "progress", 0, 65) 49 | animator.interpolator = DecelerateInterpolator() 50 | animator.duration = 1000 51 | animator.startDelay = 500 52 | animator.addListener(object : Animator.AnimatorListener { 53 | override fun onAnimationRepeat(animation: Animator) { 54 | 55 | } 56 | 57 | override fun onAnimationEnd(animation: Animator) { 58 | seekBar.visibility = View.VISIBLE 59 | } 60 | 61 | override fun onAnimationCancel(animation: Animator) { 62 | } 63 | 64 | override fun onAnimationStart(animation: Animator) { 65 | } 66 | 67 | }) 68 | 69 | animator.start() 70 | 71 | seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { 72 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { 73 | sportView.progress = progress 74 | } 75 | 76 | override fun onStartTrackingTouch(seekBar: SeekBar?) { 77 | } 78 | 79 | override fun onStopTrackingTouch(seekBar: SeekBar?) { 80 | } 81 | 82 | }) 83 | 84 | 85 | } 86 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/SportView.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.util.AttributeSet 6 | import android.view.View 7 | import com.starts.hencoderview.util.dp2px 8 | import com.starts.hencoderview.util.sp 9 | 10 | /** 11 | 12 | *文件描述:. 13 | *作者:Created by Administrator on 2020/4/29. 14 | *版本号:1.0 15 | 16 | */ 17 | class SportView(context: Context, attrs: AttributeSet) : View(context, attrs) { 18 | 19 | private val outCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG) 20 | private val intCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG) 21 | private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG) 22 | 23 | lateinit var pointCenter: Point 24 | 25 | val radius = dp2px(100) 26 | 27 | val lineWidth = dp2px(15).toFloat() 28 | 29 | lateinit var rectF: RectF 30 | 31 | var progress = 0 32 | get() { 33 | return field 34 | } 35 | set(value) { 36 | field = value 37 | invalidate() 38 | } 39 | 40 | private val maxProgress = 100f 41 | 42 | private var bounds: Rect 43 | 44 | 45 | init { 46 | outCirclePaint.color = Color.parseColor("#cccccc") 47 | intCirclePaint.color = Color.parseColor("#45A4FF") 48 | textPaint.color = Color.parseColor("#45A4FF") 49 | 50 | outCirclePaint.strokeWidth = lineWidth 51 | intCirclePaint.strokeWidth = lineWidth 52 | 53 | outCirclePaint.strokeCap = Paint.Cap.ROUND 54 | intCirclePaint.strokeCap = Paint.Cap.ROUND 55 | 56 | outCirclePaint.style = Paint.Style.STROKE 57 | intCirclePaint.style = Paint.Style.STROKE 58 | 59 | textPaint.textSize = sp(32) 60 | 61 | bounds = Rect() 62 | } 63 | 64 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { 65 | super.onSizeChanged(w, h, oldw, oldh) 66 | pointCenter = Point(w / 2, h / 2) 67 | rectF = RectF( 68 | pointCenter.x - radius.toFloat(), 69 | pointCenter.y - radius.toFloat(), 70 | pointCenter.x + radius.toFloat(), 71 | pointCenter.y + radius.toFloat() 72 | ) 73 | } 74 | 75 | override fun onDraw(canvas: Canvas) { 76 | super.onDraw(canvas) 77 | canvas.drawCircle( 78 | pointCenter.x.toFloat(), 79 | pointCenter.y.toFloat(), 80 | radius.toFloat(), 81 | outCirclePaint 82 | ) 83 | 84 | val angle = progress / maxProgress * 360 85 | canvas.drawArc(rectF, 270f, angle, false, intCirclePaint) 86 | 87 | textPaint.getTextBounds("${progress}%", 0, "${progress}%".length, bounds) 88 | canvas.drawText( 89 | "${progress}%", 90 | 0, 91 | "${progress}%".length, 92 | pointCenter.x - bounds.width() / 2f, 93 | pointCenter.y + bounds.height() / 2f, 94 | textPaint 95 | ) 96 | 97 | } 98 | 99 | 100 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/FragmentTestScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.viewpager2.adapter.FragmentStateAdapter 9 | import com.bytedance.scene.ktx.requireFragmentActivity 10 | import com.bytedance.scene.ui.template.AppCompatScene 11 | import com.starts.hencoderview.databinding.SceneViewpagerTestBinding 12 | import com.starts.hencoderview.ui.EmptyFragment 13 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 14 | 15 | /** 16 | 17 | *文件描述:. 18 | *作者:Created by Administrator on 2020/7/16. 19 | *版本号:1.0 20 | 21 | */ 22 | class FragmentTestScene : AppCompatScene() { 23 | 24 | 25 | var f1: Fragment? = null 26 | var f2: Fragment? = null 27 | 28 | lateinit var binding: SceneViewpagerTestBinding 29 | 30 | override fun onCreateContentView( 31 | inflater: LayoutInflater, 32 | container: ViewGroup, 33 | savedInstanceState: Bundle? 34 | ): View { 35 | binding = SceneViewpagerTestBinding.inflate(inflater) 36 | return binding.root 37 | // return inflater.inflate(R.layout.scene_viewpager_test,null) 38 | } 39 | 40 | override fun onActivityCreated(savedInstanceState: Bundle?) { 41 | super.onActivityCreated(savedInstanceState) 42 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 43 | val fragments = arrayListOf(EmptyFragment(),EmptyFragment(),EmptyFragment(),EmptyFragment(),EmptyFragment()) 44 | binding.bt1.setOnClickListener { 45 | val b = requireFragmentActivity().supportFragmentManager.beginTransaction() 46 | if (f1 == null) { 47 | f1 = EmptyFragment() 48 | b.add(binding.viewPager1.id, f1!!) 49 | } else { 50 | b.show(f1!!) 51 | f2?.let { 52 | b.hide(it) 53 | } 54 | } 55 | b.commitAllowingStateLoss() 56 | } 57 | binding.bt2.setOnClickListener { 58 | val b = requireFragmentActivity().supportFragmentManager.beginTransaction() 59 | if (f2 == null) { 60 | f2 = EmptyFragment() 61 | b.add(binding.viewPager1.id, f2!!) 62 | } else { 63 | b.show(f2!!) 64 | f1?.let { 65 | b.hide(it) 66 | } 67 | 68 | } 69 | b.commitAllowingStateLoss() 70 | } 71 | binding.viewPager2.offscreenPageLimit = 1 72 | binding.viewPager2.isUserInputEnabled = false 73 | 74 | binding.viewPager2.adapter = object : FragmentStateAdapter(requireFragmentActivity()) { 75 | override fun getItemCount(): Int { 76 | return fragments.size 77 | } 78 | 79 | override fun createFragment(position: Int): Fragment { 80 | return EmptyFragment() 81 | } 82 | 83 | } 84 | 85 | binding.bt3.setOnClickListener { 86 | binding.viewPager2.currentItem = 0 87 | } 88 | 89 | binding.bt4.setOnClickListener { 90 | binding.viewPager2.setCurrentItem(2,false) 91 | } 92 | 93 | } 94 | 95 | 96 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/WaveViewScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.graphics.Color 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import com.bytedance.scene.ui.template.AppCompatScene 9 | import com.starts.hencoderview.databinding.SceneWaveViewBinding 10 | import com.starts.hencoderview.ui.ScalePageTransformer 11 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 12 | import com.starts.hencoderview.util.dp2px 13 | import com.starts.hencoderview.view.ViewPager2LoopAdapter 14 | import com.starts.hencoderview.view.ViewPager2LoopViewHolder 15 | import com.zhpan.bannerview.BannerViewPager 16 | import com.zhpan.indicator.enums.IndicatorSlideMode 17 | import com.zhpan.indicator.enums.IndicatorStyle 18 | 19 | /** 20 | 21 | *文件描述:. 22 | *作者:Created by Administrator on 2020/11/10. 23 | *版本号:1.0 24 | 25 | */ 26 | class WaveViewScene : AppCompatScene() { 27 | lateinit var binding: SceneWaveViewBinding 28 | 29 | private val bannerAdapter = ViewPager2LoopAdapter() 30 | 31 | private lateinit var banner: BannerViewPager 32 | 33 | 34 | private val imageList = arrayListOf( 35 | "https://pic2.zhimg.com/80/v2-f6466210c615e67f70cb8c1f4bf4621f_720w.jpg?source=1940ef5c", 36 | "https://pic2.zhimg.com/80/v2-40a15ca300e5c32eb58bab6b54614cd8_720w.jpg?source=1940ef5c", 37 | "https://pic1.zhimg.com/80/v2-c3e869051a97652c5cd0289e8a9005a4_720w.jpg?source=1940ef5c", 38 | "https://pic1.zhimg.com/80/v2-361f4e84b5e728232485356ee50f2fe8_720w.jpg?source=1940ef5c", 39 | "https://pic2.zhimg.com/80/v2-c9cd38c46a5828d9875be3461229924f_720w.jpg?source=1940ef5c", 40 | "https://pic1.zhimg.com/80/v2-0efb007b8ea5f888bd5fe8de34611b9e_720w.jpg?source=1940ef5c", 41 | "https://pic2.zhimg.com/80/v2-561182aaf0345f806cd2e2df734e4ea8_720w.jpg?source=1940ef5c", 42 | "https://pic1.zhimg.com/80/v2-524e5a7f3589575ad0162d0c0fea4f22_720w.jpg?source=1940ef5c" 43 | ) 44 | 45 | override fun onCreateContentView( 46 | inflater: LayoutInflater, 47 | container: ViewGroup, 48 | savedInstanceState: Bundle? 49 | ): View { 50 | binding = SceneWaveViewBinding.inflate(inflater) 51 | banner = binding.banner as BannerViewPager 52 | return binding.root 53 | } 54 | 55 | override fun onActivityCreated(savedInstanceState: Bundle?) { 56 | super.onActivityCreated(savedInstanceState) 57 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 58 | 59 | banner.apply { 60 | adapter = bannerAdapter 61 | setAutoPlay(true) 62 | setLifecycleRegistry(lifecycle) 63 | setIndicatorStyle(IndicatorStyle.ROUND_RECT) 64 | setIndicatorSliderGap(dp2px(5)) 65 | setIndicatorMargin(0, 0, 0, dp2px(16)) 66 | setIndicatorSlideMode(IndicatorSlideMode.NORMAL) 67 | setIndicatorSliderRadius(dp2px(10), dp2px(5)) 68 | setPageTransformer(ScalePageTransformer()) 69 | setScrollDuration(800) 70 | setIndicatorSliderColor(Color.parseColor("#80ffffff"), Color.WHITE) 71 | }.create(imageList) 72 | binding.waveView.bindViewPager(banner) 73 | } 74 | 75 | 76 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/FloatRecyclerScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.core.view.isGone 8 | import androidx.fragment.app.Fragment 9 | import androidx.recyclerview.widget.LinearLayoutManager 10 | import androidx.recyclerview.widget.RecyclerView 11 | import androidx.viewpager2.adapter.FragmentStateAdapter 12 | import com.bytedance.scene.ktx.requireFragmentActivity 13 | import com.bytedance.scene.ui.template.AppCompatScene 14 | import com.starts.hencoderview.R 15 | import com.starts.hencoderview.container.MovieDetailContainer 16 | import com.starts.hencoderview.ui.ListFragment 17 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 18 | import com.starts.hencoderview.view.ColoredTextView 19 | 20 | /** 21 | 22 | *文件描述:. 23 | *作者:Created by lorizhao on 2022/3/1. 24 | *版本号:1.0 25 | 26 | */ 27 | class FloatRecyclerScene : AppCompatScene() { 28 | lateinit var binding: MovieDetailContainer 29 | override fun onCreateContentView( 30 | inflater: LayoutInflater, 31 | container: ViewGroup, 32 | savedInstanceState: Bundle? 33 | ): View { 34 | binding = MovieDetailContainer(context = container.context) 35 | return binding 36 | } 37 | 38 | override fun onActivityCreated(savedInstanceState: Bundle?) { 39 | super.onActivityCreated(savedInstanceState) 40 | setToolbarVisible(false) 41 | setStatusBarVisible(false) 42 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 43 | val data = arrayListOf("第一个","2000","346","50000","2000","346","50000","2000","346","50000","2000","346","50000","2000","346","50000","2000","346","50000","最后3个","最后2个","最后一个") 44 | val adapter = InnerAdapter(data) 45 | binding.topRecyclerView .adapter = adapter 46 | binding.topRecyclerView.layoutManager = LinearLayoutManager(requireSceneContext()) 47 | // val titles = arrayListOf("影评","讨论") 48 | val fragments = arrayListOf(ListFragment()) 49 | val viewPagerAdapter = object :FragmentStateAdapter(requireFragmentActivity()){ 50 | override fun getItemCount(): Int { 51 | return fragments.size 52 | } 53 | 54 | override fun createFragment(position: Int): Fragment { 55 | return fragments[position] 56 | } 57 | } 58 | binding.bottomSheetLayout.bottomTabLayout.isGone = true 59 | binding.bottomSheetLayout.bottomViewPager.adapter = viewPagerAdapter 60 | // TabLayoutMediator(binding.bottomSheetLayout.bottomTabLayout,binding.bottomSheetLayout.bottomViewPager){tab,positon-> 61 | // tab.text = titles[positon] 62 | // }.attach() 63 | } 64 | } 65 | 66 | class InnerAdapter(private val data:List) : RecyclerView.Adapter(){ 67 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InnerHolder { 68 | return InnerHolder(LayoutInflater.from(parent.context).inflate(R.layout.holder_bootom_sheet,parent,false)) 69 | } 70 | 71 | override fun onBindViewHolder(holder: InnerHolder, position: Int) { 72 | holder.tvText.text = data[position] 73 | } 74 | 75 | override fun getItemCount(): Int { 76 | return data.size 77 | } 78 | 79 | 80 | } 81 | 82 | class InnerHolder(item:View) : RecyclerView.ViewHolder(item){ 83 | val tvText: ColoredTextView = item.findViewById(R.id.tvText) 84 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/scene/FlipBoardScene.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.scene 2 | 3 | import android.animation.Animator 4 | import android.animation.AnimatorSet 5 | import android.animation.ObjectAnimator 6 | import android.os.Bundle 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.view.animation.DecelerateInterpolator 11 | import android.view.animation.LinearInterpolator 12 | import android.widget.SeekBar 13 | import com.bytedance.scene.ui.template.AppCompatScene 14 | import com.starts.hencoderview.R 15 | import com.starts.hencoderview.util.ARGUMENTS_STRING_TITLE 16 | import com.starts.hencoderview.view.FlipBoardView 17 | 18 | 19 | /** 20 | 21 | *文件描述:. 22 | *作者:Created by Administrator on 2020/4/30. 23 | *版本号:1.0 24 | 25 | */ 26 | class FlipBoardScene : AppCompatScene() { 27 | private lateinit var flipView:FlipBoardView 28 | lateinit var seekRotate: SeekBar 29 | lateinit var seekCamera:SeekBar 30 | override fun onCreateContentView( 31 | inflater: LayoutInflater, 32 | container: ViewGroup, 33 | savedInstanceState: Bundle? 34 | ): View { 35 | val view = inflater.inflate(R.layout.scene_flip_bord_view, null, false) 36 | flipView = view.findViewById(R.id.flipView) 37 | seekRotate = view.findViewById(R.id.seekRotate) 38 | seekCamera = view.findViewById(R.id.seekCamera) 39 | return view 40 | } 41 | 42 | override fun onActivityCreated(savedInstanceState: Bundle?) { 43 | super.onActivityCreated(savedInstanceState) 44 | setToolbarVisible(true) 45 | setStatusBarVisible(true) 46 | setTitle(requireArguments().getString(ARGUMENTS_STRING_TITLE)) 47 | 48 | val cameraAnimator = ObjectAnimator.ofFloat(flipView, "cameraAngle", 0f, 45f) 49 | cameraAnimator.interpolator = LinearInterpolator() 50 | cameraAnimator.duration = 300 51 | 52 | val animator = ObjectAnimator.ofFloat(flipView, "angle", 0f, 360f) 53 | animator.interpolator = DecelerateInterpolator() 54 | animator.duration = 1000 55 | 56 | val animatorSet = AnimatorSet() 57 | animatorSet.playSequentially(cameraAnimator, animator) 58 | animatorSet.startDelay = 500 59 | animatorSet.start() 60 | animatorSet.addListener(object : Animator.AnimatorListener { 61 | override fun onAnimationStart(animation: Animator) { 62 | } 63 | 64 | override fun onAnimationEnd(animation: Animator) { 65 | } 66 | 67 | override fun onAnimationCancel(animation: Animator) { 68 | } 69 | 70 | override fun onAnimationRepeat(animation: Animator) { 71 | } 72 | 73 | }) 74 | 75 | seekRotate.setOnSeekBarChangeListener(object :SeekBar.OnSeekBarChangeListener{ 76 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { 77 | flipView.angle = (progress/100f) * 360 78 | } 79 | 80 | override fun onStartTrackingTouch(seekBar: SeekBar?) { 81 | } 82 | 83 | override fun onStopTrackingTouch(seekBar: SeekBar?) { 84 | } 85 | 86 | }) 87 | 88 | seekCamera.setOnSeekBarChangeListener(object :SeekBar.OnSeekBarChangeListener{ 89 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { 90 | flipView.cameraAngle = (progress/100f) * 90 91 | } 92 | 93 | override fun onStartTrackingTouch(seekBar: SeekBar?) { 94 | } 95 | 96 | override fun onStopTrackingTouch(seekBar: SeekBar?) { 97 | } 98 | 99 | }) 100 | } 101 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HenCoderView 2 | 工作过程中和平时,针对自定义View进行的实践和总结 3 | 4 | ### 双RecyclerView浮层联动 5 | [![双RecyclerView浮层联动](https://s1.ax1x.com/2022/04/16/LtkGhF.gif)](https://imgtu.com/i/LtkGhF) 6 | 7 | 仿照豆瓣详情页写的滚动视图,不过豆瓣本身是做的两个View,一个用来拖拽,一个用来滑动,这里做了个升级,直接用一个ViewGroup来实现拖动和无缝滚动 8 | 动图中显示效果为两个RecyclerView实现,内部可以无限制使用类似多类型Adapter方面的库,任意组装数据,构造多类型复杂列表 9 | 10 | ### 思路: 11 | 1. 通过自定义ViewGroup来实现布局部分,onMeasure和onLayout中自定义RecyclerView的测绘和布局 12 | 2. 通过NestScrollParent接口处理滚动联动和fling的分发 13 | 3. 通过ViewDragHelper来处理拖拽 14 | 4. 通过事件分发机制解决事件冲突,处理重叠部分的拖拽冲突 15 | 16 | ### 轮播图的水波纹背景 17 | ![轮播图的水波纹背景](https://ftp.bmp.ovh/imgs/2020/11/3286b2d39b8cd13d.gif) 18 | 19 | 项目中实际应用的轮播图水波纹背景,将图片的保存使用LurCache单独存放,在实际开发过程中可以使用图片加载框架, 20 | 加载网络图片到内存,充当背景,也可以自己修改逻辑使用纯色暂时充当背景 21 | ### 思路: 22 | 1. 通过BitmapCache单独使用图片的存取功能 23 | 2. 通过在registerOnPageChangeCallback中使用下面的代码来判断是向左滑动中还是向右滑动中 24 | 25 | 26 | if(positionOffset != 0f && positionOffsetPixels != 0){ 27 | if (position + positionOffset > sumPositionAndPositionOffset) { 28 | //right to left 29 | 30 | fromRightToLeft = true 31 | currentPosition = fixPosition(position)ø 32 | nextPosition = fixPosition(currentPosition + 1) 33 | 34 | } else { 35 | calculateDirection() 36 | //left to right 37 | 38 | fromRightToLeft = false 39 | currentPosition = fixPosition(position + 1) 40 | nextPosition = fixPosition(position) 41 | 42 | } 43 | sumPositionAndPositionOffset = position + positionOffset 44 | }else{ 45 | //最后一刻,当前位置和下一个位置 ,则根据position来确定 46 | currentPosition = position 47 | nextPo 48 | sition = fixPosition(position + 1) 49 | } 50 | 51 | 52 | 3. 为避免越界使用fixPosition()方法控制越界情况 53 | 4. 在onDraw()方法中,使用创建的canvas和bitmap,对要操作的bitmap进行绘制,并使用PorterDuffXfermode(PorterDuff.Mode.SRC_IN) , (PorterDuff.Mode.DST_OVER) 54 | 注意: 55 | 1. 在使用过程中,先画的是目标图像,后画的源图像,使用过程中注意使用离屏缓冲,避免port的使用失效 56 | 2. port的原理是在融合出,通过算法处理颜色保留 57 | 58 | ### 网易云特效 59 | ![网易云特效](https://ftp.bmp.ovh/imgs/2020/11/794e1bec8869a26f.gif) 60 | 61 | ### 思路: 62 | 1. 封装Particle粒子类(小的圆点),主要是用来保存每个点的坐标,速度,角度,透明度,偏移量 63 | 2. 使用for循环,通过使用drawCircle,500个圆点根据x,y坐标进行绘制(测量绘制时间,毫秒数小于16,绘制很安全) 64 | 3. 通过ValueAnimator,控制选点从中心向周围飘散 65 | 4. 使用三角函数,计算x,y坐标随时间推移的下一个点 66 | 67 | 68 | ### 红板报特效 69 | ![红板报](https://ftp.bmp.ovh/imgs/2020/11/f81a25d05eea4d04.gif) 70 | ### 思路: 71 | 1. 将 bitmap分为两部分看, 一部分为被camera翻转的部分,一部分为正常部分 72 | 2. 先调试,使用裁切api分成两部分 73 | 3. 然后使用camera绘制被翻转的一部分 74 | 4. 在固定绘制另外一部分 75 | 5. 最后使用动画控制旋转的角度,来达到效果 76 | 77 | 78 | ### Taglayout 79 | ![TagLayout](https://ftp.bmp.ovh/imgs/2020/11/99b24a4f62fb1578.gif) 80 | ### 思路: 81 | 1. 整体上,是测绘完所有子View,得到子View的坐标点,然后保存起来,在onlayout中使用 82 | 2. 通过for循环,遍历所有子View,通过measureChildWithMargins方法,讲View的宽高,配合剩余的在纵横两个方向上的空间,得到子View的合适位置 83 | 3. 判断是否超过ViewGroup的最大空间 84 | 4. 在onLayout中使用 85 | 86 | 87 | ### ScaleImageView 88 | ![ScaleImageView](https://ftp.bmp.ovh/imgs/2020/11/e08163e6fa0f32ca.gif) 89 | ### 思路: 90 | 1. 在绘制方法中,使用drawBitmap 配合 translate scale 方法,对Bitmap进行操作 91 | 2. 在onTouchEvent中,使用GestureDetectorCompat对View收到的事件进行捕捉,包括,双击,按压,fling 92 | 3. 通过控制变量offsetX offsetY ,实现偏移操作,在onFling方法,使用postOnAnimation 推进动画 93 | 94 | 95 | ### 环形进度条 96 | 97 | ![](https://ftp.bmp.ovh/imgs/2020/11/d518f5ad94529b68.gif) 98 | 99 | ### 思路: 100 | 1. 先画原,再画弧线(进度条完成) 101 | 2. 文字要根据实际需求,通过getTextBounds判断画文字的位置,因为文字不一样,可能导致中心点变化(基线的固定,和文字是否越过基线) 102 | 3. 通过drawText 绘制文字 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/PathView.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.util.AttributeSet 6 | import android.view.MotionEvent 7 | import android.view.View 8 | import com.starts.hencoderview.util.dp2px 9 | import com.starts.hencoderview.util.getMaterialColor 10 | import kotlin.math.min 11 | 12 | /** 13 | 14 | *文件描述:. 15 | *作者:Created by LostStars on 2020/11/26. 16 | *版本号:1.0 17 | 18 | */ 19 | class PathView : View { 20 | 21 | private var path = Path() 22 | private val darkPaint = Paint(Paint.ANTI_ALIAS_FLAG) 23 | private val lightPaint = Paint(Paint.ANTI_ALIAS_FLAG) 24 | var startPercentage = 20 25 | set(value) { 26 | field = value 27 | calculationPath() 28 | } 29 | 30 | var endPercentage = 80 31 | set(value) { 32 | if(value>startPercentage){ 33 | field = value 34 | calculationPath() 35 | 36 | } 37 | } 38 | 39 | val lightPoints = ArrayList() 40 | val darkPoints = ArrayList() 41 | 42 | private val singleDistance = 1 43 | 44 | init { 45 | darkPaint.style = Paint.Style.STROKE 46 | darkPaint.strokeWidth = dp2px(4) * 1f 47 | darkPaint.color = getMaterialColor(resources,3) 48 | 49 | lightPaint.style = Paint.Style.STROKE 50 | lightPaint.strokeWidth = dp2px(4) * 1f 51 | lightPaint.color = getMaterialColor(resources,4) 52 | 53 | } 54 | 55 | constructor(context: Context?) : super(context) 56 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 57 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( 58 | context, 59 | attrs, 60 | defStyleAttr 61 | ) 62 | 63 | constructor( 64 | context: Context?, 65 | attrs: AttributeSet?, 66 | defStyleAttr: Int, 67 | defStyleRes: Int 68 | ) : super(context, attrs, defStyleAttr, defStyleRes) 69 | 70 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { 71 | super.onSizeChanged(w, h, oldw, oldh) 72 | } 73 | 74 | override fun onTouchEvent(event: MotionEvent): Boolean { 75 | when(event.action){ 76 | MotionEvent.ACTION_DOWN->{ 77 | path.moveTo(event.x , event.y) 78 | return true 79 | } 80 | MotionEvent.ACTION_MOVE->{ 81 | path.lineTo(event.x,event.y) 82 | } 83 | MotionEvent.ACTION_UP->{ 84 | calculationPath() 85 | } 86 | MotionEvent.ACTION_CANCEL->{ 87 | 88 | invalidate() 89 | } 90 | } 91 | 92 | return super.onTouchEvent(event) 93 | } 94 | 95 | override fun onDraw(canvas: Canvas) { 96 | super.onDraw(canvas) 97 | canvas.drawPath(path,darkPaint) 98 | canvas.drawPoints(lightPoints.toFloatArray(),lightPaint) 99 | } 100 | 101 | 102 | private fun calculationPath(){ 103 | val pathMeasure = PathMeasure(path ,false) 104 | val pathLength = pathMeasure.length 105 | val count = (pathLength/singleDistance).toInt() 106 | var currentDistance = 0f 107 | for (i in 0 until count){ 108 | currentDistance+=singleDistance 109 | val point = FloatArray(2) 110 | pathMeasure.getPosTan(currentDistance,point,null) 111 | darkPoints.add(point[0]) 112 | darkPoints.add(point[1]) 113 | } 114 | val start = (darkPoints.size * startPercentage*1f /100).toInt() 115 | val end = min((darkPoints.size * endPercentage *1f/100).toInt(), darkPoints.size-1) 116 | lightPoints.clear() 117 | lightPoints.addAll(darkPoints.subList(start,end)) 118 | invalidate() 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/lineralayout_ui_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 18 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 37 | 42 | 43 | 52 | 53 | 57 | 58 | 69 | 70 | 84 | 85 | 86 | 87 | 88 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_douban_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 30 | 31 | 44 | 45 | 56 | 57 | 67 | 68 | 77 | 78 | 90 | 91 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/CustomUITest.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.graphics.Canvas 6 | import android.graphics.Color 7 | import android.graphics.Outline 8 | import android.graphics.Paint 9 | import android.util.AttributeSet 10 | import android.view.View 11 | import android.view.ViewOutlineProvider 12 | import android.widget.ImageView 13 | import android.widget.TextView 14 | import androidx.core.content.ContextCompat 15 | 16 | import com.google.android.material.floatingactionbutton.FloatingActionButton 17 | import com.starts.hencoderview.R 18 | import timber.log.Timber 19 | 20 | class CustomUITest(context: Context) : CustomLayout(context) { 21 | 22 | val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 23 | color = ContextCompat.getColor(context, R.color.pink_300) 24 | textSize = 32f 25 | } 26 | private var measureCount = 0 27 | init { 28 | setWillNotDraw(false) 29 | } 30 | private val commonMargin = 12.dp 31 | private val topImg = TestImageView(context).apply { 32 | scaleType = ImageView.ScaleType.CENTER_CROP 33 | layoutParams = LayoutParams(matchParent, 280.dp) 34 | setImageResource(R.mipmap.personal_bg) 35 | this@CustomUITest.addView(this) 36 | } 37 | private val avatar = TestImageView(context).apply { 38 | layoutParams = LayoutParams(60.dp, 60.dp) 39 | setImageResource(R.mipmap.avatar) 40 | outlineProvider = object : ViewOutlineProvider() { 41 | override fun getOutline(view: View, outline: Outline) { 42 | outline.setRoundRect(0, 0, view.width, view.height, 60f); } 43 | } 44 | this@CustomUITest.addView(this) 45 | } 46 | private val floatingActionButton = TestFloatActionButton(context).apply { 47 | layoutParams = LayoutParams(wrapContent, wrapContent) 48 | this@CustomUITest.addView(this) 49 | } 50 | private val title = TestTextView(context).apply { 51 | layoutParams = LayoutParams(wrapContent, wrapContent) 52 | text = "UIShare" 53 | textSize = 18f 54 | this@CustomUITest.addView(this) 55 | } 56 | private val introduce = TestTextView(context).apply { 57 | layoutParams = LayoutParams(wrapContent, wrapContent) 58 | text = "在Android界面的展示中,我们常见工作流程是在xml写好布局文件,之后在Activity或者fragment中展示一个UI界面, 画面需要经" + 59 | "过xml解析,之后window要通过setView方法实际构造各个View对象,并且按照层级排列好,之后Android系统再找到合适" + 60 | "的时机,触发measure,layout,draw这一流程,但是随着布局嵌套的加深,xml的解析越来越复杂,测绘的次数会逐步提高,逐渐到" + 61 | "达失控的地步。那么,需要一种方式可以一次性解决这个问题,让我们的布局不再需要多次测量来确定自己的宽高,答案就是完全自己" + 62 | "接管ViewGroup的onMeasure,onLayout过" 63 | this@CustomUITest.addView(this) 64 | } 65 | private val share = TestImageView(context).apply { 66 | layoutParams = LayoutParams(24.dp, 24.dp) 67 | setImageResource(R.mipmap.share) 68 | this@CustomUITest.addView(this) 69 | } 70 | 71 | override fun onMeasureChildren(widthMeasureSpec: Int, heightMeasureSpec: Int): Dimension { 72 | Timber.tag("onMeasure").d("measureCount = ${measureCount++} hashcode = ${this.hashCode()}") 73 | topImg.autoMeasure() 74 | avatar.autoMeasure() 75 | floatingActionButton.autoMeasure() 76 | title.autoMeasure() 77 | share.autoMeasure() 78 | val spaceHeight = (MeasureSpec.getSize(heightMeasureSpec) - 79 | title.measuredHeight - 80 | topImg.measuredHeight - 81 | commonMargin * 2).toExactlyMeasureSpec() 82 | val spaceWidth = (MeasureSpec.getSize(widthMeasureSpec) - avatar.measuredWidth - share.measuredWidth - 4 * commonMargin).toExactlyMeasureSpec() 83 | introduce.measure(spaceWidth, spaceHeight) 84 | return Dimension(widthMeasureSpec, heightMeasureSpec) 85 | } 86 | 87 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { 88 | topImg.layout(l, t) 89 | avatar.layout(commonMargin, topImg.bottom + commonMargin) 90 | floatingActionButton.layout( 91 | commonMargin, 92 | topImg.bottom - avatar.measuredHeight / 2, 93 | true 94 | ) 95 | title.layout(avatar.right + commonMargin, topImg.bottom + commonMargin) 96 | introduce.layout(avatar.right + commonMargin, title.bottom + commonMargin) 97 | share.layout(commonMargin, introduce.top, true) 98 | } 99 | 100 | override fun onDraw(canvas: Canvas) { 101 | super.onDraw(canvas) 102 | canvas.drawText("c=$measureCount" , 40f,40f,paint) 103 | } 104 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/util/QMUIAlignMiddleImageSpan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making QMUI_Android available. 3 | * 4 | * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the MIT License (the "License"); you may not use this file except in 7 | * compliance with the License. You may obtain a copy of the License at 8 | * 9 | * http://opensource.org/licenses/MIT 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 12 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.starts.hencoderview.util; 18 | 19 | import android.graphics.Canvas; 20 | import android.graphics.Paint; 21 | import android.graphics.Rect; 22 | import android.graphics.drawable.Drawable; 23 | import android.text.style.ImageSpan; 24 | 25 | /** 26 | * 支持垂直居中的ImageSpan 27 | * 28 | * @author cginechen 29 | * @date 2016-03-17 30 | */ 31 | public class QMUIAlignMiddleImageSpan extends ImageSpan { 32 | 33 | public static final int ALIGN_MIDDLE = -100; // 不要和父类重复 34 | 35 | /** 36 | * 规定这个Span占几个字的宽度 37 | */ 38 | private float mFontWidthMultiple = -1f; 39 | 40 | /** 41 | * 是否避免父类修改FontMetrics,如果为 false 则会走父类的逻辑, 会导致FontMetrics被更改 42 | */ 43 | private boolean mAvoidSuperChangeFontMetrics = false; 44 | 45 | @SuppressWarnings("FieldCanBeLocal") private int mWidth; 46 | 47 | /** 48 | * @param d 作为 span 的 Drawable 49 | * @param verticalAlignment 垂直对齐方式, 如果要垂直居中, 则使用 {@link #ALIGN_MIDDLE} 50 | */ 51 | public QMUIAlignMiddleImageSpan(Drawable d, int verticalAlignment) { 52 | super(d, verticalAlignment); 53 | } 54 | 55 | /** 56 | * @param d 作为 span 的 Drawable 57 | * @param verticalAlignment 垂直对齐方式, 如果要垂直居中, 则使用 {@link #ALIGN_MIDDLE} 58 | * @param fontWidthMultiple 设置这个Span占几个中文字的宽度, 当该值 > 0 时, span 的宽度为该值*一个中文字的宽度; 当该值 <= 0 时, span 的宽度由 {@link #mAvoidSuperChangeFontMetrics} 决定 59 | */ 60 | public QMUIAlignMiddleImageSpan(Drawable d, int verticalAlignment, float fontWidthMultiple) { 61 | this(d, verticalAlignment); 62 | if (fontWidthMultiple >= 0) { 63 | mFontWidthMultiple = fontWidthMultiple; 64 | } 65 | } 66 | 67 | @Override 68 | public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { 69 | if (mAvoidSuperChangeFontMetrics) { 70 | Drawable d = getDrawable(); 71 | Rect rect = d.getBounds(); 72 | mWidth = rect.right; 73 | } else { 74 | mWidth = super.getSize(paint, text, start, end, fm); 75 | } 76 | if (mFontWidthMultiple > 0) { 77 | mWidth = (int) (paint.measureText("子") * mFontWidthMultiple); 78 | } 79 | return mWidth; 80 | } 81 | 82 | @Override 83 | public void draw(Canvas canvas, CharSequence text, int start, int end, 84 | float x, int top, int y, int bottom, Paint paint) { 85 | if (mVerticalAlignment == ALIGN_MIDDLE) { 86 | Drawable d = getDrawable(); 87 | canvas.save(); 88 | 89 | // // 注意如果这样实现会有问题:TextView 有 lineSpacing 时,这里 bottom 偏大,导致偏下 90 | // int transY = bottom - d.getBounds().bottom; // 底对齐 91 | // transY -= (paint.getFontMetricsInt().bottom - paint.getFontMetricsInt().top) / 2 - d.getBounds().bottom / 2; // 居中对齐 92 | // canvas.translate(x, transY); 93 | // d.draw(canvas); 94 | // canvas.restore(); 95 | 96 | Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt(); 97 | int fontTop = y + fontMetricsInt.top; 98 | int fontMetricsHeight = fontMetricsInt.bottom - fontMetricsInt.top; 99 | int iconHeight = d.getBounds().bottom - d.getBounds().top; 100 | int iconTop = fontTop + (fontMetricsHeight - iconHeight) / 2; 101 | canvas.translate(x, iconTop); 102 | d.draw(canvas); 103 | canvas.restore(); 104 | } else { 105 | super.draw(canvas, text, start, end, x, top, y, bottom, paint); 106 | } 107 | } 108 | 109 | /** 110 | * 是否避免父类修改FontMetrics,如果为 false 则会走父类的逻辑, 会导致FontMetrics被更改 111 | */ 112 | public void setAvoidSuperChangeFontMetrics(boolean avoidSuperChangeFontMetrics) { 113 | mAvoidSuperChangeFontMetrics = avoidSuperChangeFontMetrics; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/container/CustomLayout.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.container 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import android.util.AttributeSet 6 | import android.util.Log 7 | import android.view.HapticFeedbackConstants 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import androidx.core.view.* 11 | 12 | @Suppress("MemberVisibilityCanBePrivate") 13 | abstract class CustomLayout : ViewGroup { 14 | 15 | 16 | protected val Int.dp: Int get() = (this * resources.displayMetrics.density + 0.5f).toInt() 17 | 18 | constructor(context: Context?) : super(context) 19 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 20 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( 21 | context, 22 | attrs, 23 | defStyleAttr 24 | ) 25 | 26 | class LayoutParams(width: Int, height: Int) : MarginLayoutParams(width, height) 27 | 28 | override fun generateDefaultLayoutParams(): LayoutParams { 29 | return LayoutParams(matchParent, wrapContent) 30 | } 31 | 32 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 33 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 34 | val dimension = this.onMeasureChildren(widthMeasureSpec, heightMeasureSpec) 35 | setMeasuredDimension(dimension.width, dimension.height) 36 | } 37 | 38 | protected data class Dimension(val width: Int, val height: Int) 39 | 40 | protected abstract fun onMeasureChildren( 41 | widthMeasureSpec: Int, 42 | heightMeasureSpec: Int 43 | ): Dimension 44 | 45 | protected fun View.autoMeasure() { 46 | if (isGone) return 47 | measure( 48 | this.defaultWidthMeasureSpec(parentView = this@CustomLayout), 49 | this.defaultHeightMeasureSpec(parentView = this@CustomLayout) 50 | ) 51 | } 52 | 53 | protected fun View.forEachAutoMeasure() { 54 | forEach { it.autoMeasure() } 55 | } 56 | 57 | protected fun View.layout(x: Int, y: Int, fromRight: Boolean = false) { 58 | if (isGone) return 59 | if (!fromRight) { 60 | layout(x, y, x + measuredWidth, y + measuredHeight) 61 | } else { 62 | layout(this@CustomLayout.measuredWidth - x - measuredWidth, y) 63 | } 64 | } 65 | 66 | protected val View.measuredWidthWithMargins get() = (measuredWidth + marginLeft + marginRight) 67 | protected val View.measuredHeightWithMargins get() = (measuredHeight + marginTop + marginBottom) 68 | 69 | protected fun View.defaultWidthMeasureSpec(parentView: ViewGroup): Int { 70 | return when (layoutParams.width) { 71 | ViewGroup.LayoutParams.MATCH_PARENT -> parentView.measuredWidth.toExactlyMeasureSpec() 72 | ViewGroup.LayoutParams.WRAP_CONTENT -> Int.MAX_VALUE.toAtMostMeasureSpec() 73 | 0 -> throw IllegalAccessException("Need special treatment for $this") 74 | else -> layoutParams.width.toExactlyMeasureSpec() 75 | } 76 | } 77 | 78 | protected fun View.defaultHeightMeasureSpec(parentView: ViewGroup): Int { 79 | return when (layoutParams.height) { 80 | ViewGroup.LayoutParams.MATCH_PARENT -> parentView.measuredHeight.toExactlyMeasureSpec() 81 | ViewGroup.LayoutParams.WRAP_CONTENT -> Int.MAX_VALUE.toAtMostMeasureSpec() 82 | 0 -> throw IllegalAccessException("Need special treatment for $this") 83 | else -> layoutParams.height.toExactlyMeasureSpec() 84 | } 85 | } 86 | 87 | protected fun Int.toExactlyMeasureSpec(): Int { 88 | return MeasureSpec.makeMeasureSpec(this, MeasureSpec.EXACTLY) 89 | } 90 | 91 | protected fun Int.toAtMostMeasureSpec(): Int { 92 | return MeasureSpec.makeMeasureSpec(this, MeasureSpec.AT_MOST) 93 | } 94 | 95 | fun addView(child: View, width: Int, height: Int, apply: (LayoutParams.() -> Unit)) { 96 | val params = generateDefaultLayoutParams() 97 | params.width = width 98 | params.height = height 99 | apply(params) 100 | super.addView(child, params) 101 | } 102 | 103 | fun View.overScrollNever() { 104 | overScrollMode = View.OVER_SCROLL_NEVER 105 | } 106 | 107 | fun ViewGroup.horizontalCenterX(child: View): Int { 108 | return (measuredWidth - child.measuredWidth) / 2 109 | } 110 | 111 | fun ViewGroup.verticalCenterTop(child: View): Int { 112 | return (measuredHeight - child.measuredHeight) / 2 113 | } 114 | } 115 | 116 | const val matchParent = ViewGroup.LayoutParams.MATCH_PARENT 117 | const val wrapContent = ViewGroup.LayoutParams.WRAP_CONTENT 118 | 119 | fun View.transparentBackground() { 120 | setBackgroundColor(Color.TRANSPARENT) 121 | } 122 | 123 | val View.parentView get() = parent as ViewGroup 124 | 125 | fun View?.performHapticFeedbackSafely() { 126 | try { 127 | this?.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) 128 | } catch (t: Throwable) { 129 | Log.e("CustomLayout", t.toString()) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/CustomLayout.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import android.util.AttributeSet 6 | import android.view.HapticFeedbackConstants 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import androidx.core.view.* 10 | import timber.log.Timber 11 | 12 | @Suppress("MemberVisibilityCanBePrivate") 13 | abstract class CustomLayout : ViewGroup { 14 | 15 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 16 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 17 | val dimension = this.onMeasureChildren(widthMeasureSpec, heightMeasureSpec) 18 | setMeasuredDimension(dimension.width, dimension.height) 19 | } 20 | 21 | protected data class Dimension(val width: Int, val height: Int) 22 | 23 | protected abstract fun onMeasureChildren( 24 | widthMeasureSpec: Int, 25 | heightMeasureSpec: Int 26 | ): Dimension 27 | 28 | protected fun View.autoMeasure() { 29 | if (isGone) return 30 | measure( 31 | this.defaultWidthMeasureSpec(parentView = this@CustomLayout), 32 | this.defaultHeightMeasureSpec(parentView = this@CustomLayout) 33 | ) 34 | } 35 | 36 | 37 | protected fun View.defaultWidthMeasureSpec(parentView: ViewGroup): Int { 38 | return when (layoutParams.width) { 39 | ViewGroup.LayoutParams.MATCH_PARENT -> parentView.measuredWidth.toExactlyMeasureSpec() 40 | ViewGroup.LayoutParams.WRAP_CONTENT -> ViewGroup.LayoutParams.WRAP_CONTENT.toAtMostMeasureSpec() 41 | 0 -> throw IllegalAccessException("Need special treatment for $this") 42 | else -> layoutParams.width.toExactlyMeasureSpec() 43 | } 44 | } 45 | 46 | protected val Int.dp: Int get() = (this * resources.displayMetrics.density + 0.5f).toInt() 47 | 48 | constructor(context: Context?) : super(context) 49 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 50 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( 51 | context, 52 | attrs, 53 | defStyleAttr 54 | ) 55 | 56 | class LayoutParams(width: Int, height: Int) : MarginLayoutParams(width, height) 57 | 58 | override fun generateDefaultLayoutParams(): LayoutParams { 59 | return LayoutParams(matchParent, wrapContent) 60 | } 61 | 62 | 63 | 64 | protected fun View.defaultHeightMeasureSpec(parentView: ViewGroup): Int { 65 | return when (layoutParams.height) { 66 | ViewGroup.LayoutParams.MATCH_PARENT -> parentView.measuredHeight.toExactlyMeasureSpec() 67 | ViewGroup.LayoutParams.WRAP_CONTENT -> Int.MAX_VALUE.toAtMostMeasureSpec() 68 | 0 -> throw IllegalAccessException("Need special treatment for $this") 69 | else -> layoutParams.height.toExactlyMeasureSpec() 70 | } 71 | } 72 | 73 | 74 | protected fun View.forEachAutoMeasure() { 75 | forEach { it.autoMeasure() } 76 | } 77 | 78 | protected fun View.layout(x: Int, y: Int, fromRight: Boolean = false) { 79 | if (isGone) return 80 | if (!fromRight) { 81 | layout(x, y, x + measuredWidth, y + measuredHeight) 82 | } else { 83 | layout(this@CustomLayout.measuredWidth - x - measuredWidth, y) 84 | } 85 | } 86 | 87 | protected val View.measuredWidthWithMargins get() = (measuredWidth + marginLeft + marginRight) 88 | protected val View.measuredHeightWithMargins get() = (measuredHeight + marginTop + marginBottom) 89 | 90 | 91 | 92 | protected fun Int.toExactlyMeasureSpec(): Int { 93 | return MeasureSpec.makeMeasureSpec(this, MeasureSpec.EXACTLY) 94 | } 95 | 96 | protected fun Int.toAtMostMeasureSpec(): Int { 97 | return MeasureSpec.makeMeasureSpec(this, MeasureSpec.AT_MOST) 98 | } 99 | 100 | fun addView(child: View, width: Int, height: Int, apply: (LayoutParams.() -> Unit)) { 101 | val params = generateDefaultLayoutParams() 102 | params.width = width 103 | params.height = height 104 | apply(params) 105 | super.addView(child, params) 106 | } 107 | 108 | fun View.overScrollNever() { 109 | overScrollMode = View.OVER_SCROLL_NEVER 110 | } 111 | 112 | fun ViewGroup.horizontalCenterX(child: View): Int { 113 | return (measuredWidth - child.measuredWidth) / 2 114 | } 115 | 116 | fun View.horizontalCenterX(child: View): Int { 117 | return (measuredWidth - child.measuredWidth) / 2 118 | } 119 | 120 | fun ViewGroup.verticalCenterTop(child: View): Int { 121 | return (measuredHeight - child.measuredHeight) / 2 122 | } 123 | 124 | fun View.verticalCenterTop(child: View): Int { 125 | return (measuredHeight - child.measuredHeight) / 2 126 | } 127 | } 128 | 129 | const val matchParent = ViewGroup.LayoutParams.MATCH_PARENT 130 | const val wrapContent = ViewGroup.LayoutParams.WRAP_CONTENT 131 | 132 | fun View.transparentBackground() { 133 | setBackgroundColor(Color.TRANSPARENT) 134 | } 135 | 136 | val View.parentView get() = parent as ViewGroup 137 | 138 | fun View?.performHapticFeedbackSafely() { 139 | try { 140 | this?.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) 141 | } catch (t: Throwable) { 142 | Timber.tag("CustomLayout").e(t.toString()) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/ParticleScatteringView.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.animation.ValueAnimator 4 | import android.content.Context 5 | import android.graphics.* 6 | import android.util.AttributeSet 7 | import android.util.Log 8 | import android.view.View 9 | import android.view.animation.LinearInterpolator 10 | import com.starts.hencoderview.R 11 | import kotlin.math.* 12 | import kotlin.random.Random 13 | import kotlin.system.measureTimeMillis 14 | 15 | /** 16 | 17 | *文件描述:. 18 | *作者:Created by Administrator on 2020/10/23. 19 | *版本号:1.0 20 | 21 | */ 22 | class ParticleScatteringView(context: Context, attrs: AttributeSet?) : View(context, attrs) { 23 | 24 | var viewWidth = 0 25 | var viewHeight = 0 26 | 27 | 28 | var imageAngle = 0f 29 | 30 | var centerX = 0f 31 | var centerY = 0f 32 | 33 | val imagePaint = Paint(Paint.ANTI_ALIAS_FLAG) 34 | lateinit var imageShader: BitmapShader 35 | 36 | //粒子的数量 37 | val particleCount = 500 38 | val particlePaint = Paint(Paint.ANTI_ALIAS_FLAG) 39 | 40 | 41 | val imageBitmap: Bitmap by lazy { 42 | val option = BitmapFactory.Options() 43 | option.outWidth = (viewWidth * 1f / 2).toInt() 44 | option.outHeight = (viewHeight * 1f / 2).toInt() 45 | BitmapFactory.decodeResource(context.resources, R.mipmap.ui_scale_img, option) 46 | } 47 | 48 | 49 | val bitmapCanvas = Canvas() 50 | //粒子集合 51 | val particleList = ArrayList() 52 | 53 | private var particleAnimator = ValueAnimator.ofFloat(0f, 1f) 54 | 55 | private var innerCirclePath = Path() 56 | private var pathMeasure = PathMeasure() 57 | 58 | init { 59 | particleAnimator.duration = 4000 60 | particleAnimator.repeatCount = -1 61 | particleAnimator.interpolator = LinearInterpolator() 62 | particleAnimator.addUpdateListener { 63 | updateParticle() 64 | imageAngle = 360 * it.animatedValue as Float 65 | invalidate()//重绘界面 66 | } 67 | } 68 | 69 | private fun updateParticle() { 70 | particleList.forEach { 71 | if (it.offset > it.maxOffset) { 72 | it.offset = 0f 73 | it.speed = (Random.nextInt(2, 4)).toFloat() 74 | } 75 | it.alpha = ((1f - it.offset / it.maxOffset) * 225f).toInt() 76 | 77 | it.offset += it.speed 78 | // if(it.offset >= it.maxOffset){ 79 | // return 80 | // }else{ 81 | it.x = centerX + cos(it.angle) * (310f + it.offset) 82 | 83 | if (it.y > centerY) { 84 | it.y = (sin(it.angle) * (310f + it.offset) + centerY) 85 | } else { 86 | it.y = (centerY - sin(it.angle) * (310f + it.offset)) 87 | } 88 | 89 | it.offset += it.speed 90 | 91 | } 92 | } 93 | 94 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { 95 | super.onSizeChanged(w, h, oldw, oldh) 96 | viewWidth = w 97 | viewHeight = h 98 | 99 | centerX = viewWidth * 1f / 2 100 | centerY = viewHeight * 1f / 2 101 | 102 | innerCirclePath.addCircle( 103 | centerX, 104 | centerY, 105 | 310f, Path.Direction.CCW 106 | ) 107 | 108 | pathMeasure.setPath(innerCirclePath, false) 109 | val singleLength = pathMeasure.length / particleCount 110 | 111 | 112 | 113 | 114 | imageShader = BitmapShader(imageBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) 115 | imagePaint.shader = imageShader 116 | 117 | 118 | particlePaint.color = Color.WHITE 119 | particlePaint.style = Paint.Style.FILL 120 | for (i in 0..particleCount) { 121 | val particle = Particle() 122 | val localArray = FloatArray(2) 123 | val tanArray = FloatArray(2) 124 | 125 | if (pathMeasure.getPosTan(i * singleLength, localArray, tanArray)) { 126 | particle.x = localArray[0] + (Random.nextInt(10) - 20) 127 | particle.y = localArray[1] + (Random.nextInt(10) - 20) 128 | } 129 | 130 | particle.speed = (Random.nextInt(2, 4)).toFloat() 131 | particle.angle = acos((localArray[0] - centerX) / 280f) 132 | particleList.add(particle) 133 | } 134 | particleAnimator.start() 135 | 136 | } 137 | 138 | val bitmap:Bitmap by lazy { 139 | Bitmap.createBitmap(viewWidth, viewHeight, 140 | Bitmap.Config.ARGB_8888) 141 | } 142 | 143 | override fun onDraw(canvas: Canvas) { 144 | super.onDraw(canvas) 145 | canvas.save() 146 | 147 | canvas.rotate(imageAngle , centerX , centerY) 148 | canvas.drawCircle( 149 | centerX, 150 | centerY, 151 | 300f, 152 | imagePaint 153 | ) 154 | canvas.restore() 155 | val time = measureTimeMillis { 156 | particleList.forEach { 157 | particlePaint.alpha = it.alpha 158 | canvas.drawCircle(it.x, it.y, it.radius, particlePaint) 159 | } 160 | } 161 | 162 | Log.d("ParticleScatteringView", "time = $time") 163 | 164 | } 165 | 166 | class Particle { 167 | var x: Float = 0f//X坐标 168 | var y: Float = 0f//Y坐标 169 | var radius: Float = 3f//半径 170 | var speed: Float = 0f//速度 171 | var alpha: Int = 1//透明度 172 | var angle = 0f 173 | val maxOffset = 150f 174 | var offset = 0f 175 | } 176 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/starts/hencoderview/view/ScaleImageView.kt: -------------------------------------------------------------------------------- 1 | package com.starts.hencoderview.view 2 | 3 | import android.animation.ObjectAnimator 4 | import android.content.Context 5 | import android.graphics.Bitmap 6 | import android.graphics.BitmapFactory 7 | import android.graphics.Canvas 8 | import android.graphics.Paint 9 | import android.util.AttributeSet 10 | import android.util.Log 11 | import android.view.GestureDetector 12 | import android.view.MotionEvent 13 | import android.view.View 14 | import android.widget.OverScroller 15 | import androidx.core.view.GestureDetectorCompat 16 | import com.starts.hencoderview.R 17 | import kotlin.math.max 18 | import kotlin.math.min 19 | 20 | /** 21 | 22 | *文件描述:. 23 | *作者:Created by Administrator on 2020/5/20. 24 | *版本号:1.0 25 | 26 | */ 27 | class ScaleImageView(context: Context, attrs: AttributeSet) : View(context, attrs), 28 | GestureDetector.OnDoubleTapListener, GestureDetector.OnGestureListener , Runnable { 29 | 30 | private var originalOffsetX = 0f 31 | private var originalOffsetY = 0f 32 | 33 | private var currentScale = 0f 34 | set(value) { 35 | field = value 36 | invalidate() 37 | } 38 | private var bigScale = 0f 39 | private var smallScale = 0f 40 | 41 | private val OVER_SCALE_FACTOR = 1.2f 42 | 43 | private var isBig = false 44 | 45 | private var offsetX = 0f 46 | private var offsetY = 0f 47 | 48 | private val gestureDetector: GestureDetectorCompat by lazy { 49 | GestureDetectorCompat(context, this) 50 | } 51 | 52 | private val scroller = OverScroller(context) 53 | 54 | private val scaleAnimation: ObjectAnimator by lazy { 55 | ObjectAnimator.ofFloat(this, "currentScale", smallScale, bigScale) 56 | } 57 | 58 | private val paint = Paint(Paint.ANTI_ALIAS_FLAG) 59 | private val bitmap: Bitmap by lazy { 60 | BitmapFactory.decodeResource(context.resources, R.mipmap.ui_scale_img_2) 61 | } 62 | 63 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { 64 | super.onSizeChanged(w, h, oldw, oldh) 65 | 66 | originalOffsetX = (width * 1f - bitmap.width) / 2f 67 | originalOffsetY = (height * 1f - bitmap.height) / 2f 68 | 69 | if (bitmap.width * 1f / bitmap.height > width * 1f / height) { 70 | smallScale = width * 1f / bitmap.width 71 | bigScale = height * 1f / bitmap.height * OVER_SCALE_FACTOR 72 | } else { 73 | smallScale = height * 1f / bitmap.height 74 | bigScale = width * 1f / bitmap.width * OVER_SCALE_FACTOR 75 | } 76 | currentScale = smallScale 77 | } 78 | 79 | override fun onDraw(canvas: Canvas) { 80 | super.onDraw(canvas) 81 | val scaleFraction = (currentScale - smallScale) / (bigScale - smallScale) 82 | canvas.translate(offsetX * scaleFraction, offsetY * scaleFraction) 83 | //参数里的 sx sy 是横向和纵向的放缩倍数; px py 是放缩的轴心。 84 | canvas.scale(currentScale, currentScale, width / 2f, height / 2f) 85 | canvas.drawBitmap(bitmap, originalOffsetX, originalOffsetY, paint) 86 | 87 | } 88 | 89 | override fun onTouchEvent(event: MotionEvent): Boolean { 90 | return gestureDetector.onTouchEvent(event) 91 | } 92 | 93 | override fun onDoubleTap(e: MotionEvent): Boolean { 94 | isBig = !isBig 95 | if (isBig) { 96 | scaleAnimation.start() 97 | } else { 98 | scaleAnimation.reverse() 99 | } 100 | return false 101 | } 102 | 103 | override fun onDoubleTapEvent(e: MotionEvent) = false 104 | override fun onSingleTapConfirmed(e: MotionEvent) = false 105 | override fun onShowPress(e: MotionEvent) { 106 | 107 | } 108 | 109 | override fun onSingleTapUp(e: MotionEvent): Boolean = false 110 | 111 | override fun onDown(e: MotionEvent) = true 112 | 113 | override fun onFling( 114 | e1: MotionEvent, 115 | e2: MotionEvent, 116 | velocityX: Float, 117 | velocityY: Float 118 | ): Boolean { 119 | if (isBig) { 120 | scroller.fling( 121 | offsetX.toInt(), 122 | offsetY.toInt(), 123 | velocityX.toInt(), 124 | velocityY.toInt(), 125 | (-(bitmap.width * bigScale - width * 1f) / 2).toInt(), 126 | ((bitmap.width * bigScale - width * 1f) / 2).toInt(), 127 | (-(bitmap.height * bigScale - height * 1f) / 2).toInt(), 128 | ((bitmap.height * bigScale - height * 1f) / 2).toInt(), 129 | 100, 130 | 100 131 | ) 132 | postOnAnimation(this) 133 | 134 | } 135 | 136 | 137 | return false 138 | } 139 | 140 | override fun onScroll( 141 | e1: MotionEvent, 142 | e2: MotionEvent, 143 | distanceX: Float, 144 | distanceY: Float 145 | ): Boolean { 146 | Log.d("TEST" ,"distanceY = $distanceY,distanceX = $distanceX") 147 | if (isBig) { 148 | offsetX -= distanceX 149 | offsetY -= distanceY 150 | fixOffsets() 151 | Log.d("onScroll", "distanceX:${distanceX} , distanceY:${distanceY}") 152 | invalidate() 153 | } 154 | return false 155 | } 156 | 157 | override fun onLongPress(e: MotionEvent) { 158 | 159 | } 160 | 161 | private fun fixOffsets() { 162 | offsetX = min(offsetX, (bitmap.width * bigScale - width * 1f) / 2) 163 | offsetX = max(offsetX, -(bitmap.width * bigScale - width * 1f) / 2) 164 | 165 | offsetY = min(offsetY, (bitmap.height * bigScale - height * 1f) / 2) 166 | offsetY = max(offsetY, -(bitmap.height * bigScale - height * 1f) / 2) 167 | 168 | } 169 | 170 | override fun run() { 171 | if(scroller.computeScrollOffset()){ 172 | offsetX = scroller.currX.toFloat() 173 | offsetY = scroller.currY.toFloat() 174 | invalidate() 175 | postOnAnimation(this) 176 | } 177 | } 178 | 179 | 180 | } -------------------------------------------------------------------------------- /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/layout/google_contrain_demo.xml: -------------------------------------------------------------------------------- 1 | 13 | 19 | 20 | 33 | 34 | 47 | 48 | 58 | 59 | 71 | 72 | 81 | 82 | 91 | 92 | 104 | 105 |