├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── starts │ │ └── hencoderview │ │ └── ExampleInstrumentedTest.kt │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── com │ │ └── starts │ │ └── hencoderview │ │ ├── HenApplication.kt │ │ ├── MainActivity.kt │ │ ├── SpanUtils.java │ │ ├── container │ │ ├── BottomSheetLayout.kt │ │ ├── CustomLayout.kt │ │ └── MovieDetailContainer.kt │ │ ├── remote │ │ └── ApiService.kt │ │ ├── scene │ │ ├── ConstrainCompareScene.kt │ │ ├── ConstrainTestScene.kt │ │ ├── CustomLayoutScene.kt │ │ ├── CustomUIScene.kt │ │ ├── EllipsizedTextScene.kt │ │ ├── FlipBoardScene.kt │ │ ├── FloatRecyclerScene.kt │ │ ├── FragmentTestScene.kt │ │ ├── LinearLayoutScene.kt │ │ ├── LinearTestScene.kt │ │ ├── MainScene.kt │ │ ├── ParticleScatteringScene.kt │ │ ├── PathScene.kt │ │ ├── RelateLayoutScene.kt │ │ ├── ScaleAlphaAnimScene.kt │ │ ├── ScaleImageScene.kt │ │ ├── SportScene.kt │ │ ├── TagLayoutScene.kt │ │ ├── TransitionScene.kt │ │ ├── ViewBindingScene.kt │ │ ├── ViewGroupScene.kt │ │ └── WaveViewScene.kt │ │ ├── ui │ │ ├── EmptyFragment.kt │ │ ├── ListFragment.kt │ │ ├── MemoryLeakActivity.kt │ │ ├── ScalePageTransformer.java │ │ └── ViewPagerBottomSheetBehavior.kt │ │ ├── util │ │ ├── Ext.kt │ │ └── QMUIAlignMiddleImageSpan.java │ │ └── view │ │ ├── ColoredTextView.java │ │ ├── CustomLayout.kt │ │ ├── CustomUITest.kt │ │ ├── EllipsizeIconTextView.kt │ │ ├── FlipBoardView.kt │ │ ├── ParticleScatteringView.kt │ │ ├── PathLayoutManager.kt │ │ ├── PathView.kt │ │ ├── PreViewOutLineProvider.kt │ │ ├── ScaleImageView.kt │ │ ├── SportView.kt │ │ ├── TagLayout.kt │ │ ├── TestButton.kt │ │ ├── TestConstraintLayout.kt │ │ ├── TestEditText.kt │ │ ├── TestFloatActionButton.kt │ │ ├── TestImageView.kt │ │ ├── TestLinearLayout.kt │ │ ├── TestTextView.kt │ │ ├── TestViewLayout.kt │ │ ├── TransitionLayout.kt │ │ └── WaveView.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── ic_launcher_background.xml │ ├── ic_star.png │ ├── info_background.xml │ ├── layer_list_progress_drawable.xml │ ├── playing_com_into.png │ ├── shape_progressbar_bg.xml │ ├── shape_progressbar_progress.xml │ ├── shape_white.xml │ └── singapore.jpg │ ├── layout │ ├── activity_main.xml │ ├── activity_memory_lead.xml │ ├── chin_demo.xml │ ├── constrain_compare_layout.xml │ ├── constrain_layout_demo.xml │ ├── demo_main_holder.xml │ ├── fragment_empty.xml │ ├── fragment_list.xml │ ├── google_contrain_demo.xml │ ├── holder_bootom_sheet.xml │ ├── layout_barrier_demo.xml │ ├── lineralayout_ui_test.xml │ ├── radio_demo.xml │ ├── relatelayout_gone_test.xml │ ├── relatelayout_ui_test.xml │ ├── scene_constraint_layout_test.xml │ ├── scene_ellipsized_text.xml │ ├── scene_flip_bord_view.xml │ ├── scene_linear_layout_test.xml │ ├── scene_music_album_detail.xml │ ├── scene_particle_scattering.xml │ ├── scene_path.xml │ ├── scene_scale_alpha.xml │ ├── scene_scale_image_view.xml │ ├── scene_sport_view.xml │ ├── scene_tag_layout.xml │ ├── scene_viewpager_test.xml │ ├── scene_wave_view.xml │ ├── view_douban_bottom_content.xml │ ├── view_douban_toolbar.xml │ └── view_pager.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── avatar.webp │ ├── ic_launcher.png │ ├── ic_launcher_round.png │ ├── img_egg.jpg │ ├── personal_bg.webp │ ├── red.png │ ├── share.webp │ ├── ui_scale_img.jpg │ └── ui_scale_img_2.jpg │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimen.xml │ ├── material_colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.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 | -------------------------------------------------------------------------------- /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/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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 | } -------------------------------------------------------------------------------- /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/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/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 | } -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhaoSiBo/HenCoderView/9fb1557a4c1fcfcea9e1334545bde840fa3652a4/app/src/main/res/drawable/ic_star.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/info_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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/drawable/playing_com_into.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhaoSiBo/HenCoderView/9fb1557a4c1fcfcea9e1334545bde840fa3652a4/app/src/main/res/drawable/playing_com_into.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/singapore.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhaoSiBo/HenCoderView/9fb1557a4c1fcfcea9e1334545bde840fa3652a4/app/src/main/res/drawable/singapore.jpg -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_memory_lead.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/chin_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 23 | 24 | 36 | 37 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /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/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/demo_main_holder.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | -------------------------------------------------------------------------------- /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 |