├── .gitignore
├── .idea
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── dbnavigator.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── DUGIFMaker
├── font
│ ├── Elsie-Black.otf
│ ├── HappyMonkey-Regular.ttf
│ ├── Lobster-1.3.ttf
│ ├── MacondoSwashCaps-Regular.ttf
│ ├── Merriweather-Bold.ttf
│ ├── Nautilus.otf
│ ├── Roboto-Medium.ttf
│ ├── Titillium-Bold.otf
│ └── skranji-regular.ttf
└── recorded
│ ├── 20191116_182348.gif
│ ├── 20191116_182640.gif
│ └── 20191116_182846.gif
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── devyk
│ │ └── customview
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── devyk
│ │ │ └── customview
│ │ │ ├── CanvasSample.kt
│ │ │ ├── CustomDrawable.kt
│ │ │ ├── CustomViewGroup.kt
│ │ │ ├── FlowLayoutActivity.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── MapActivity.kt
│ │ │ ├── PathActivity.kt
│ │ │ ├── SVGDemo1Activity.kt
│ │ │ ├── TestActivity1.kt
│ │ │ ├── adapter
│ │ │ └── RecyclerViewAdapter.kt
│ │ │ ├── sample_1
│ │ │ ├── Button.kt
│ │ │ ├── CircleView.kt
│ │ │ ├── CircleView2.kt
│ │ │ ├── CircleView3.kt
│ │ │ ├── CustomView_1.kt
│ │ │ ├── GustomLIn.kt
│ │ │ ├── MyRecyclerView.kt
│ │ │ ├── ScrollerLayout.java
│ │ │ ├── ScrollerSample_1.kt
│ │ │ ├── ScrollerViewPager.kt
│ │ │ ├── SlideView1.kt
│ │ │ └── SlideView2.kt
│ │ │ ├── sample_2
│ │ │ └── WaterfallFlowLayout.java
│ │ │ └── utils
│ │ │ └── QMUIDisplayHelper.java
│ └── res
│ │ ├── animator
│ │ ├── anim_infinite.xml
│ │ ├── anim_search.xml
│ │ ├── anim_start.xml
│ │ ├── transit.xml
│ │ ├── tt_path_one.xml
│ │ ├── tt_path_three.xml
│ │ └── tt_path_two.xml
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── avft.png
│ │ ├── avft_active.png
│ │ ├── box_stack.png
│ │ ├── box_stack_active.png
│ │ ├── bubble_frame.png
│ │ ├── bubble_frame_active.png
│ │ ├── bubbles.png
│ │ ├── bubbles_active.png
│ │ ├── bullseye.png
│ │ ├── bullseye_active.png
│ │ ├── circle_filled.png
│ │ ├── circle_filled_active.png
│ │ ├── circle_outline.png
│ │ ├── circle_outline_active.png
│ │ ├── flag_01.xml
│ │ ├── heart.png
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_line.xml
│ │ ├── ic_police_car.xml
│ │ ├── ic_toutiao.xml
│ │ ├── line_animated_car.xml
│ │ ├── line_animated_search.xml
│ │ ├── line_animated_toutiao.xml
│ │ ├── line_animated_vector.xml
│ │ └── search_svg.xml
│ │ ├── layout
│ │ ├── activity_canvas_sample.xml
│ │ ├── activity_custom_viewgroup.xml
│ │ ├── activity_main.xml
│ │ ├── activity_map.xml
│ │ ├── activity_path.xml
│ │ ├── activity_svg.xml
│ │ ├── activity_test.xml
│ │ ├── activity_test_1.xml
│ │ └── tv.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── attrs_circle.xml
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── devyk
│ └── customview
│ └── ExampleUnitTest.kt
├── build.gradle
├── custom_view
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── devyk
│ │ └── custom_view
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── devyk
│ │ │ └── custom_view
│ │ │ ├── BitmapUtis.kt
│ │ │ ├── base
│ │ │ └── BaseView.kt
│ │ │ ├── canvas
│ │ │ ├── CircleView.kt
│ │ │ ├── ClockView.kt
│ │ │ ├── CustomDrawable.kt
│ │ │ ├── DrawColor.kt
│ │ │ ├── GallaryHorizonalScrollView.kt
│ │ │ ├── path
│ │ │ │ └── PathView.kt
│ │ │ └── path_measure
│ │ │ │ ├── CarRotate.kt
│ │ │ │ ├── FacLoadingView.kt
│ │ │ │ ├── PathMeasureView.kt
│ │ │ │ └── SpiderWebView.kt
│ │ │ ├── flow
│ │ │ ├── FlowLayout.kt
│ │ │ ├── TagAdapter.kt
│ │ │ ├── TagFlowLayout.kt
│ │ │ └── TagView.kt
│ │ │ ├── paint
│ │ │ ├── FilterView.kt
│ │ │ ├── LinearGradientTextView.kt
│ │ │ ├── MyGradientView.kt
│ │ │ ├── RadarGradientView.kt
│ │ │ ├── ShadowLayerView.kt
│ │ │ ├── XfermodeView.kt
│ │ │ ├── ZoomImageView.kt
│ │ │ └── xfermode
│ │ │ │ ├── GuaGuaCard.kt
│ │ │ │ ├── HeartView.kt
│ │ │ │ ├── InvertedImageView.kt
│ │ │ │ ├── IrregularWaveView.kt
│ │ │ │ ├── RoudImageView.kt
│ │ │ │ └── TwitterView.kt
│ │ │ ├── svg
│ │ │ ├── PathParser.java
│ │ │ └── map
│ │ │ │ ├── Dom2PathData.kt
│ │ │ │ ├── Dom2Xml.kt
│ │ │ │ ├── MapData.kt
│ │ │ │ └── MapView.kt
│ │ │ ├── utils.kt
│ │ │ └── utils
│ │ │ ├── HelpDraw.java
│ │ │ ├── HelpPath.java
│ │ │ └── Utils.java
│ └── res
│ │ ├── drawable
│ │ ├── arrow.jpg
│ │ ├── car.png
│ │ ├── circle_shape.png
│ │ ├── flag_01.xml
│ │ ├── guaguaka.jpg
│ │ ├── guaguaka_text1.png
│ │ ├── heartmap.png
│ │ ├── invert_shade.png
│ │ ├── shade.png
│ │ ├── twiter_bg.png
│ │ ├── twiter_light.png
│ │ └── wav.png
│ │ ├── layout
│ │ └── tv.xml
│ │ ├── mipmap-xhdpi
│ │ ├── gild_3.jpeg
│ │ ├── girl_gaitubao.jpg
│ │ └── heart.png
│ │ ├── raw
│ │ └── chinahigh.xml
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── devyk
│ └── custom_view
│ └── ExampleUnitTest.java
├── 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/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
12 | * author : devyk on 2019-12-01 20:22 13 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 14 | * github : https://github.com/yangkun19921001 15 | * mailbox : yang1001yk@gmail.com 16 | * desc : This is CanvasSample 17 | *18 | */ 19 | class CanvasSample : Activity() { 20 | 21 | private val mImgIds = intArrayOf(//7个 22 | R.drawable.avft, 23 | R.drawable.box_stack, 24 | R.drawable.bubble_frame, 25 | R.drawable.bubbles, 26 | R.drawable.bullseye, 27 | R.drawable.circle_filled, 28 | R.drawable.circle_outline, 29 | 30 | R.drawable.avft, 31 | R.drawable.box_stack, 32 | R.drawable.bubble_frame, 33 | R.drawable.bubbles, 34 | R.drawable.bullseye, 35 | R.drawable.circle_filled, 36 | R.drawable.circle_outline 37 | ) 38 | private val mImgIds_active = intArrayOf( 39 | R.drawable.avft_active, 40 | R.drawable.box_stack_active, 41 | R.drawable.bubble_frame_active, 42 | R.drawable.bubbles_active, 43 | R.drawable.bullseye_active, 44 | R.drawable.circle_filled_active, 45 | R.drawable.circle_outline_active, 46 | R.drawable.avft_active, 47 | R.drawable.box_stack_active, 48 | R.drawable.bubble_frame_active, 49 | R.drawable.bubbles_active, 50 | R.drawable.bullseye_active, 51 | R.drawable.circle_filled_active, 52 | R.drawable.circle_outline_active 53 | ) 54 | 55 | lateinit var revealDrawables: Array
11 | * author : devyk on 2019-12-02 17:00 12 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 13 | * github : https://github.com/yangkun19921001 14 | * mailbox : yang1001yk@gmail.com 15 | * desc : This is CustomDrawable 16 | * 17 | * 18 | *19 | */ 20 | public class CustomDrawable : Drawable { 21 | 22 | lateinit var unseleter: Drawable 23 | lateinit var selecter: Drawable 24 | 25 | constructor(unseleter: Drawable, selecter: Drawable) : super() { 26 | this.selecter = selecter 27 | this.unseleter = unseleter 28 | 29 | } 30 | 31 | 32 | override fun draw(canvas: Canvas) { 33 | //得到当前自身 Drawable 的矩形区域 34 | val bounds = bounds 35 | //1. 绘制灰色部分 36 | drawGrayDraw(bounds,canvas) 37 | //2. 绘制彩色部分 38 | drawColorDraw(bounds,canvas) 39 | } 40 | 41 | 42 | /** 43 | * 绘制灰色区域 44 | * @link https://www.cnblogs.com/over140/archive/2011/12/14/2287179.html 45 | */ 46 | private fun drawGrayDraw(bound: Rect, canvas: Canvas) { 47 | val rect = Rect() 48 | Gravity.apply( 49 | Gravity.LEFT,//开始从左边或者右边开始抠图 50 | bound.width(), //目标矩形的宽 51 | bound.height(), //目标矩形的高 52 | bound,//被扣出来的 rect 53 | rect //目标 rect 54 | ) 55 | canvas.save() //保存当前画布 56 | canvas.clipRect(rect) 57 | unseleter.draw(canvas) 58 | canvas.restore() 59 | 60 | } 61 | 62 | /** 63 | * 绘制彩色区域 64 | */ 65 | private fun drawColorDraw(bounds: Rect, canvas: Canvas) { 66 | val rect = Rect() 67 | Gravity.apply( 68 | Gravity.RIGHT,//开始从左边或者右边开始抠图 69 | bounds.width()/3, //目标矩形的宽 70 | bounds.height(), //目标矩形的高 71 | bounds,//被扣出来的 rect 72 | rect //目标 rect 73 | ) 74 | canvas.save() //保存当前画布 75 | canvas.clipRect(rect) 76 | selecter.draw(canvas) 77 | canvas.restore() 78 | 79 | } 80 | 81 | 82 | override fun setAlpha(alpha: Int) { 83 | } 84 | 85 | override fun getOpacity(): Int = 0 86 | 87 | override fun setColorFilter(colorFilter: ColorFilter?) { 88 | } 89 | 90 | override fun onBoundsChange(bounds: Rect) { 91 | super.onBoundsChange(bounds) 92 | //改变之后重新赋值 93 | unseleter.bounds = bounds 94 | selecter.bounds = bounds 95 | } 96 | 97 | override fun getIntrinsicHeight(): Int { 98 | return Math.max(selecter.intrinsicHeight, unseleter.intrinsicHeight) 99 | } 100 | 101 | override fun getIntrinsicWidth(): Int { 102 | return Math.max(selecter.intrinsicWidth, unseleter.intrinsicWidth) 103 | } 104 | 105 | override fun onLevelChange(level: Int): Boolean { 106 | //如果 level 改变,提醒自己需要重绘 107 | invalidateSelf() 108 | return super.onLevelChange(level) 109 | 110 | } 111 | 112 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/customview/CustomViewGroup.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.customview 2 | 3 | import android.app.Activity 4 | import android.graphics.Color 5 | import android.os.Bundle 6 | import android.widget.TextView 7 | import kotlinx.android.synthetic.main.activity_custom_viewgroup.* 8 | import java.util.* 9 | 10 | /** 11 | *
12 | * author : devyk on 2019-11-27 17:27 13 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 14 | * github : https://github.com/yangkun19921001 15 | * mailbox : yang1001yk@gmail.com 16 | * desc : This is CustomViewGroup 17 | *18 | */ 19 | class CustomViewGroup : Activity() { 20 | 21 | val content = mutableListOf
18 | * author : devyk on 2019-11-27 15:18 19 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 20 | * github : https://github.com/yangkun19921001 21 | * mailbox : yang1001yk@gmail.com 22 | * desc : This is FlowLayoutActivity 23 | *24 | */ 25 | class FlowLayoutActivity : Activity() { 26 | 27 | var TAG = this.javaClass.simpleName 28 | 29 | private val mVals = arrayOf( 30 | "Hello", 31 | "Android", 32 | "Weclome Hi ", 33 | "Button", 34 | "TextView", 35 | "Hello", 36 | "Android", 37 | "Weclome", 38 | "Button ImageView", 39 | "TextView", 40 | "Helloworld", 41 | "Android", 42 | "Weclome Hello", 43 | "Button Text", 44 | "TextView" 45 | ) 46 | 47 | override fun onCreate(savedInstanceState: Bundle?) { 48 | super.onCreate(savedInstanceState) 49 | setContentView(R.layout.activity_test) 50 | 51 | mFlowLayout.setAdapter(object : TagAdapter
9 | * author : devyk on 2019-12-06 15:23 10 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 11 | * github : https://github.com/yangkun19921001 12 | * mailbox : yang1001yk@gmail.com 13 | * desc : This is MapActivity 地图显示控件 14 | *15 | */ 16 | 17 | public class MapActivity : Activity() { 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN) 21 | setContentView(R.layout.activity_map) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/customview/PathActivity.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.customview 2 | 3 | import android.app.Activity 4 | import android.os.Bundle 5 | import android.view.Window 6 | import android.view.WindowManager 7 | import com.devyk.custom_view.canvas.path.PathView 8 | import com.devyk.custom_view.canvas.path_measure.CarRotate 9 | import com.devyk.custom_view.canvas.path_measure.FaceLoadingView 10 | import com.devyk.custom_view.canvas.path_measure.PathMeasureView 11 | import com.devyk.custom_view.canvas.path_measure.SpiderWebView 12 | 13 | /** 14 | *
15 | * author : devyk on 2019-12-03 16:18 16 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 17 | * github : https://github.com/yangkun19921001 18 | * mailbox : yang1001yk@gmail.com 19 | * desc : This is PathActivity 20 | *21 | */ 22 | 23 | public class PathActivity : Activity(){ 24 | 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN) 28 | // setContentView(PathView(this)) 29 | // setContentView(PathMeasureView(this)) 30 | // setContentView(CarRotate(this)) 31 | // setContentView(FaceLoadingView(this)) 32 | setContentView(SpiderWebView(this)) 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/customview/SVGDemo1Activity.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.customview 2 | 3 | import android.annotation.SuppressLint 4 | import android.graphics.drawable.Animatable 5 | import android.graphics.drawable.Drawable 6 | import android.os.Build 7 | import android.os.Bundle 8 | import android.os.Handler 9 | import android.os.Message 10 | import androidx.annotation.RequiresApi 11 | import androidx.appcompat.app.AppCompatActivity 12 | import androidx.vectordrawable.graphics.drawable.Animatable2Compat 13 | import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat 14 | import kotlinx.android.synthetic.main.activity_svg.* 15 | 16 | 17 | /** 18 | *
19 | * author : devyk on 2019-12-06 23:30 20 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 21 | * github : https://github.com/yangkun19921001 22 | * mailbox : yang1001yk@gmail.com 23 | * desc : This is SVGDemo1Activity 24 | *25 | */ 26 | class SVGDemo1Activity : AppCompatActivity() { 27 | 28 | 29 | var reStartTT = @SuppressLint("HandlerLeak") 30 | object : Handler() { 31 | override fun handleMessage(msg: Message) { 32 | super.handleMessage(msg) 33 | startAnimatabe(R.drawable.line_animated_toutiao, true) 34 | } 35 | 36 | } 37 | 38 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 39 | override fun onCreate(savedInstanceState: Bundle?) { 40 | super.onCreate(savedInstanceState) 41 | setContentView(R.layout.activity_svg) 42 | 43 | //水滴动画 44 | startWaterDropAnimator.setOnClickListener { 45 | startAnimatabe(R.drawable.line_animated_vector, false) 46 | } 47 | //搜索动画 48 | startSearchAnimator.setOnClickListener { 49 | startAnimatabe(R.drawable.line_animated_search, false) 50 | } 51 | //执行警车动画 52 | startPoliceCarAnimator.setOnClickListener { 53 | startAnimatabe(R.drawable.line_animated_car, false) 54 | } 55 | //执行头条动画 56 | startTTAnimator.setOnClickListener { 57 | startAnimatabe(R.drawable.line_animated_toutiao, true) 58 | } 59 | } 60 | 61 | private fun startAnimatabe(lineAnimatedVector: Int, isRegister: Boolean): Animatable { 62 | val animatedVectorDrawable = AnimatedVectorDrawableCompat.create(this, lineAnimatedVector) 63 | iv.setImageDrawable(animatedVectorDrawable) 64 | val animatable = iv.drawable as Animatable 65 | animatable.start() 66 | animatedVectorDrawable!!.registerAnimationCallback(object : Animatable2Compat.AnimationCallback() { 67 | override fun onAnimationEnd(drawable: Drawable?) { 68 | super.onAnimationEnd(drawable) 69 | if (!isRegister) return 70 | animatedVectorDrawable.unregisterAnimationCallback(this) 71 | //重新开始在 xml 设置 restart 无效暂时用 Handler 实现了。 72 | reStartTT.sendEmptyMessage(0) 73 | 74 | } 75 | }) 76 | return animatable 77 | 78 | } 79 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/customview/TestActivity1.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.customview 2 | 3 | import android.app.Activity 4 | import android.app.Dialog 5 | import android.os.Bundle 6 | 7 | /** 8 | *
9 | * author : devyk on 2019-11-29 22:18 10 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 11 | * github : https://github.com/yangkun19921001 12 | * mailbox : yang1001yk@gmail.com 13 | * desc : This is TestActivity1 14 | *15 | */ 16 | public class TestActivity1 : Activity(){ 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | 20 | // setContentView(R.layout.activity_test_1) 21 | // setContentView(R.layout.activity_test_1) 22 | 23 | 24 | 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/customview/adapter/RecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.customview.adapter 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.devyk.customview.R 10 | 11 | /** 12 | *
13 | * author : devyk on 2019-11-20 23:54 14 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 15 | * github : https://github.com/yangkun19921001 16 | * mailbox : yang1001yk@gmail.com 17 | * desc : This is RecyclerViewAdapter 18 | *19 | */ 20 | class RecyclerViewAdapter(list: List
10 | * author : devyk on 2019-11-17 16:15 11 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 12 | * github : https://github.com/yangkun19921001 13 | * mailbox : yang1001yk@gmail.com 14 | * desc : This is Button 15 | *16 | */ 17 | 18 | public class Button(context: Context?, attrs: AttributeSet?) : AppCompatButton(context, attrs) { 19 | 20 | 21 | override fun dispatchTouchEvent(event: MotionEvent): Boolean { 22 | when (event.action) { 23 | MotionEvent.ACTION_DOWN -> { 24 | println("事件分发机制开始分发 ----> 子View dispatchTouchEvent ACTION_DOWN") 25 | parent.requestDisallowInterceptTouchEvent(false) 26 | } 27 | MotionEvent.ACTION_MOVE -> { 28 | println("事件分发机制开始分发 ----> 子View dispatchTouchEvent ACTION_MOVE") 29 | if (true){ 30 | parent.requestDisallowInterceptTouchEvent(false) 31 | } 32 | } 33 | MotionEvent.ACTION_UP -> { 34 | println("事件分发机制开始分发 ----> 子View dispatchTouchEvent ACTION_UP") 35 | } 36 | } 37 | return super.dispatchTouchEvent(event) 38 | } 39 | 40 | override fun onTouchEvent(event: MotionEvent): Boolean { 41 | var isHandler = false 42 | when (event.action) { 43 | MotionEvent.ACTION_DOWN -> { 44 | println("事件分发机制处理 ----> 子View onTouchEvent ACTION_DOWN") 45 | isHandler = true 46 | } 47 | MotionEvent.ACTION_MOVE -> { 48 | println("事件分发机制处理 ----> 子View onTouchEvent ACTION_MOVE") 49 | isHandler = false 50 | } 51 | MotionEvent.ACTION_UP -> { 52 | println("事件分发机制处理 ----> 子View onTouchEvent ACTION_UP") 53 | isHandler = true 54 | } 55 | } 56 | return isHandler 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/customview/sample_1/CircleView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.customview.sample_1 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.view.View 9 | 10 | /** 11 | *
12 | * author : devyk on 2019-11-16 14:22 13 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 14 | * github : https://github.com/yangkun19921001 15 | * mailbox : yang1001yk@gmail.com 16 | * desc : This is CircleView 17 | *18 | */ 19 | class CircleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { 20 | 21 | var mWidth = 0; 22 | var mHeight = 0; 23 | 24 | 25 | override fun draw(canvas: Canvas) { 26 | super.draw(canvas) 27 | canvas.drawCircle(400f,400f,200f, Paint().also { 28 | it.isAntiAlias = true 29 | it.strokeWidth = 5f 30 | it.strokeCap = Paint.Cap.ROUND 31 | it.color = Color.RED 32 | }) 33 | } 34 | 35 | 36 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 37 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 38 | val widthMode = MeasureSpec.getMode(widthMeasureSpec) 39 | val heightMode = MeasureSpec.getMode(heightMeasureSpec) 40 | 41 | val widthSize = MeasureSpec.getSize(widthMeasureSpec) 42 | val heightSize = MeasureSpec.getSize(heightMeasureSpec) 43 | 44 | /** 45 | * 说明在布局中使用了 wrap_content 模式 46 | */ 47 | if (widthMeasureSpec == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST){ 48 | setMeasuredDimension(mWidth,mHeight) 49 | }else if (widthMeasureSpec == MeasureSpec.AT_MOST){ 50 | setMeasuredDimension(mWidth,heightSize) 51 | }else if (heightMeasureSpec == MeasureSpec.AT_MOST){ 52 | setMeasuredDimension(widthSize,mHeight) 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/customview/sample_1/CircleView2.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.customview.sample_1 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.view.View 9 | 10 | /** 11 | *
12 | * author : devyk on 2019-11-27 15:02 13 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 14 | * github : https://github.com/yangkun19921001 15 | * mailbox : yang1001yk@gmail.com 16 | * desc : This is CircleView2 17 | *18 | */ 19 | class CircleView2: View { 20 | 21 | val color = Color.RED 22 | 23 | val paint = Paint(Paint.ANTI_ALIAS_FLAG) 24 | 25 | constructor(context: Context) : super(context) { 26 | init() 27 | } 28 | 29 | 30 | 31 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { 32 | init() 33 | } 34 | 35 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { 36 | init() 37 | } 38 | 39 | 40 | override fun draw(canvas: Canvas) { 41 | super.draw(canvas) 42 | val height = height 43 | val width = width 44 | val radius = Math.min(width, height) / 2f 45 | 46 | canvas.drawCircle(width/2f,height/2f,radius,paint) 47 | 48 | 49 | } 50 | 51 | 52 | private fun init() { 53 | paint.setColor(color) 54 | paint.isAntiAlias = true 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/customview/sample_1/CircleView3.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.customview.sample_1 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.view.View 9 | import android.icu.lang.UCharacter.GraphemeClusterBreak.T 10 | import com.devyk.customview.R 11 | 12 | 13 | /** 14 | *
15 | * author : devyk on 2019-11-27 16:32 16 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 17 | * github : https://github.com/yangkun19921001 18 | * mailbox : yang1001yk@gmail.com 19 | * desc : This is CircleView3 20 | *21 | */ 22 | class CircleView3 : View { 23 | 24 | 25 | var color = Color.RED 26 | 27 | val paint = Paint(Paint.ANTI_ALIAS_FLAG) 28 | 29 | constructor(context: Context) : super(context) { 30 | init() 31 | } 32 | 33 | 34 | 35 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { 36 | initTypedrray(context,attrs) 37 | init() 38 | } 39 | 40 | private fun initTypedrray(context: Context, attrs: AttributeSet) { 41 | //拿到自定义属性组 42 | val obtainStyledAttributes = context.obtainStyledAttributes(attrs, R.styleable.CircleView) 43 | color = obtainStyledAttributes.getColor(R.styleable.CircleView_circle_view_color, Color.RED) 44 | obtainStyledAttributes.recycle() 45 | 46 | } 47 | 48 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { 49 | init() 50 | } 51 | 52 | 53 | /** 54 | * 解决 wrap_content 55 | */ 56 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 57 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 58 | val widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec) 59 | val widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec) 60 | val heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec) 61 | val heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec) 62 | if (widthSpecMode == View.MeasureSpec.AT_MOST && heightSpecMode == View.MeasureSpec.AT_MOST) { 63 | setMeasuredDimension(200, 200) 64 | } else if (widthSpecMode == View.MeasureSpec.AT_MOST) { 65 | setMeasuredDimension(200, heightSpecSize) 66 | } else if (heightSpecMode == View.MeasureSpec.AT_MOST) { 67 | setMeasuredDimension(widthSpecSize, 200) 68 | } 69 | } 70 | 71 | 72 | /** 73 | * 解决 padding 74 | */ 75 | override fun draw(canvas: Canvas) { 76 | super.draw(canvas) 77 | 78 | val paddingLeft = paddingLeft 79 | val paddingRight = paddingRight 80 | val paddingBottom = paddingBottom 81 | val paddingTop = paddingTop 82 | val height = height - paddingBottom - paddingTop 83 | val width = width - paddingLeft - paddingRight 84 | val radius = Math.min(width, height) / 2f 85 | 86 | canvas.drawCircle(paddingLeft + width/2f,paddingTop + height/2f,radius,paint) 87 | 88 | 89 | } 90 | 91 | 92 | private fun init() { 93 | paint.setColor(color) 94 | paint.isAntiAlias = true 95 | } 96 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/customview/sample_1/CustomView_1.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.customview.sample_1 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.view.* 9 | import android.widget.Scroller 10 | 11 | /** 12 | *
13 | * author : devyk on 2019-11-14 00:22 14 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 15 | * github : https://github.com/yangkun19921001 16 | * mailbox : yang1001yk@gmail.com 17 | * desc : This is CustomView_1 18 | *19 | */ 20 | class CustomView_1 : View { 21 | 22 | 23 | 24 | /** 25 | * 定义一个画笔 26 | */ 27 | private var pint = Paint(); 28 | 29 | private val mSollor = Scroller(context); 30 | 31 | /** 32 | * 获取最小滑动 33 | */ 34 | private var scaledDoubleTapSlop = 0 35 | 36 | /** 37 | * 速度追踪 38 | */ 39 | private lateinit var obtain :VelocityTracker 40 | 41 | /** 42 | * 手势 43 | */ 44 | private lateinit var mGetDetector : GestureDetector 45 | 46 | /** 47 | * 平滑滑动 48 | */ 49 | private lateinit var mScroller: Scroller 50 | 51 | 52 | constructor(context: Context?) : super(context) { 53 | init() 54 | } 55 | 56 | private fun init() { 57 | pint.strokeWidth = 10f 58 | pint.color = Color.RED 59 | pint.isAntiAlias = true 60 | pint.strokeCap = Paint.Cap.ROUND 61 | 62 | mScroller = Scroller(context) 63 | 64 | 65 | /** 66 | * 系统所能识别出来的被认为滑动的最小距离 67 | */ 68 | scaledDoubleTapSlop = ViewConfiguration.get(context).scaledDoubleTapSlop; 69 | mGetDetector.setIsLongpressEnabled(false) 70 | 71 | /** 72 | * 手势识别 73 | */ 74 | mGetDetector = GestureDetector(context,object :GestureDetector.OnGestureListener{ 75 | override fun onShowPress(e: MotionEvent?) { 76 | println("onShowPress") 77 | } 78 | 79 | override fun onSingleTapUp(e: MotionEvent?): Boolean { 80 | println("onSingleTapUp") 81 | return true 82 | } 83 | 84 | override fun onDown(e: MotionEvent?): Boolean { 85 | println("onDown") 86 | return true 87 | } 88 | 89 | override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean { 90 | println("onFling") 91 | return true 92 | } 93 | 94 | override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean { 95 | println("onScroll") 96 | return true 97 | } 98 | 99 | override fun onLongPress(e: MotionEvent?) { 100 | println("onLongPress") 101 | } 102 | }) 103 | } 104 | 105 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { 106 | init() 107 | } 108 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { 109 | init() 110 | } 111 | 112 | 113 | override fun onDraw(canvas: Canvas) { 114 | super.onDraw(canvas) 115 | canvas.drawCircle(400f, 400f, 200f, pint) 116 | 117 | 118 | val x = x 119 | val y = y 120 | val translationX = translationX 121 | val translationY = translationY 122 | 123 | println("x:$x y:$y translationX:$translationX translationY:$translationY") 124 | 125 | 126 | } 127 | 128 | 129 | var lastX = 0 130 | var lasty = 0 131 | 132 | override fun onTouchEvent(event: MotionEvent): Boolean { 133 | var rawX = event.getRawX() 134 | var rawY = event.getRawY() 135 | 136 | 137 | 138 | when (event?.action) { 139 | MotionEvent.ACTION_DOWN -> { 140 | /** 141 | * 速度追踪 142 | */ 143 | obtain = VelocityTracker.obtain() 144 | obtain.addMovement(event) 145 | println("ACTION_DOWN getX:$scrollX getY:$y") 146 | } 147 | MotionEvent.ACTION_UP -> { 148 | 149 | var targetIndex = (getScrollX() + getWidth() / 2) / getWidth(); 150 | var dx = targetIndex * getWidth() - getScrollX(); 151 | // 第二步,调用startScroll()方法来初始化滚动数据并刷新界面 152 | mScroller.startScroll(-100, 0, dx - 100, 0); 153 | invalidate(); 154 | 155 | } 156 | MotionEvent.ACTION_MOVE -> { 157 | obtain.computeCurrentVelocity(1000) 158 | val xVelocity = obtain.getXVelocity() 159 | val yVelocity = obtain.getYVelocity() 160 | val curY = -(rawY - lasty) 161 | val curX = -(rawX- lastX) 162 | 163 | println("curX:$curX curY:$curY") 164 | obtain.clear() 165 | scrollBy(-curX.toInt(),-curY.toInt()) 166 | } 167 | } 168 | lastX = rawX.toInt() 169 | lasty = rawY.toInt() 170 | return true 171 | 172 | } 173 | 174 | 175 | 176 | 177 | override fun computeScroll() { 178 | super.computeScroll() 179 | // 第三步,重写computeScroll()方法,并在其内部完成平滑滚动的逻辑 180 | if (mScroller.computeScrollOffset()) { 181 | scrollTo(-100,-100); 182 | invalidate(); 183 | } 184 | } 185 | 186 | 187 | 188 | 189 | 190 | 191 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/customview/sample_1/GustomLIn.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.customview.sample_1 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.MotionEvent 6 | import android.widget.LinearLayout 7 | 8 | /** 9 | *
10 | * author : devyk on 2019-11-17 16:15 11 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 12 | * github : https://github.com/yangkun19921001 13 | * mailbox : yang1001yk@gmail.com 14 | * desc : This is Button 15 | *16 | */ 17 | 18 | public class GustomLIn(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) { 19 | 20 | /** 21 | * 是否需要拦截父容器的点击事件方法 22 | */ 23 | private var isIntercepted = false 24 | 25 | /** 26 | * 记录上一下的坐标 27 | */ 28 | private var mLastXIntercept = 0f 29 | private var mLastYIntercept = 0f 30 | 31 | override fun onTouchEvent(event: MotionEvent): Boolean { 32 | if (event.action == MotionEvent.ACTION_DOWN) 33 | println("事件分发机制处理 ----> 父容器 LinearLayout onTouchEvent") 34 | return false 35 | } 36 | 37 | override fun dispatchTouchEvent(ev: MotionEvent): Boolean { 38 | if (ev.action == MotionEvent.ACTION_DOWN) 39 | println("事件分发机制开始分发 ----> 父容器 dispatchTouchEvent") 40 | return super.dispatchTouchEvent(ev) 41 | } 42 | 43 | override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { 44 | when (ev.action) { 45 | MotionEvent.ACTION_DOWN -> { 46 | isIntercepted = false 47 | println("事件分发机制开始分发 ----> onInterceptTouchEvent ACTION_DOWN") 48 | } 49 | MotionEvent.ACTION_MOVE -> { 50 | //拦截子类的移动事件 51 | if (true) { 52 | println("事件分发机制开始分发 ----> 拦截子类的移动事件 onInterceptTouchEvent") 53 | isIntercepted = true 54 | } else { 55 | isIntercepted = false 56 | } 57 | 58 | } 59 | MotionEvent.ACTION_UP -> { 60 | isIntercepted = false 61 | println("事件分发机制开始分发 ----> onInterceptTouchEvent ACTION_UP") 62 | } 63 | } 64 | return isIntercepted 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/customview/sample_1/MyRecyclerView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.customview.sample_1 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.util.Log 6 | import android.view.MotionEvent 7 | import android.view.View 8 | import androidx.recyclerview.widget.RecyclerView 9 | 10 | /** 11 | *
12 | * author : devyk on 2019-11-20 00:08 13 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 14 | * github : https://github.com/yangkun19921001 15 | * mailbox : yang1001yk@gmail.com 16 | * desc : This is HorizontalScrollView 17 | *18 | */ 19 | class MyRecyclerView(context: Context, attrs: AttributeSet?) : RecyclerView(context, attrs) { 20 | 21 | /** 22 | * 分别记录我们上次滑动的坐标 23 | */ 24 | private var mLastX = 0; 25 | private var mLastY = 0; 26 | 27 | constructor(context: Context) : this(context, null) 28 | 29 | 30 | /** 31 | * 重写分发事件 32 | */ 33 | override fun dispatchTouchEvent(ev: MotionEvent): Boolean { 34 | val x = ev.getX().toInt() 35 | val y = ev.getY().toInt() 36 | 37 | when (ev.action) { 38 | MotionEvent.ACTION_DOWN -> { 39 | var par = parent as ScrollerViewPager 40 | //请求父类不要拦截事件 41 | par.requestDisallowInterceptTouchEvent(true) 42 | Log.d("dispatchTouchEvent", "---》子ACTION_DOWN"); 43 | } 44 | MotionEvent.ACTION_MOVE -> { 45 | val deltaX = x - mLastX 46 | val deltaY = y - mLastY 47 | 48 | if (Math.abs(deltaX) > Math.abs(deltaY)){ 49 | var par = parent as ScrollerViewPager 50 | Log.d("dispatchTouchEvent", "dx:" + deltaX + " dy:" + deltaY); 51 | //交于父类来处理 52 | par.requestDisallowInterceptTouchEvent(false) 53 | } 54 | } 55 | MotionEvent.ACTION_UP -> { 56 | } 57 | } 58 | 59 | 60 | mLastX = x 61 | mLastY = y 62 | return super.dispatchTouchEvent(ev) 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/customview/sample_1/ScrollerLayout.java: -------------------------------------------------------------------------------- 1 | package com.devyk.customview.sample_1; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.MotionEvent; 6 | import android.view.View; 7 | import android.view.ViewConfiguration; 8 | import android.view.ViewGroup; 9 | import android.widget.Scroller; 10 | import androidx.core.view.ViewConfigurationCompat; 11 | 12 | /** 13 | * Created by guolin on 16/1/12. 14 | */ 15 | public class ScrollerLayout extends ViewGroup { 16 | 17 | /** 18 | * 用于完成滚动操作的实例 19 | */ 20 | private Scroller mScroller; 21 | 22 | /** 23 | * 判定为拖动的最小移动像素数 24 | */ 25 | private int mTouchSlop; 26 | 27 | /** 28 | * 手机按下时的屏幕坐标 29 | */ 30 | private float mXDown; 31 | 32 | /** 33 | * 手机当时所处的屏幕坐标 34 | */ 35 | private float mXMove; 36 | 37 | /** 38 | * 上次触发ACTION_MOVE事件时的屏幕坐标 39 | */ 40 | private float mXLastMove; 41 | 42 | /** 43 | * 界面可滚动的左边界 44 | */ 45 | private int leftBorder; 46 | 47 | /** 48 | * 界面可滚动的右边界 49 | */ 50 | private int rightBorder; 51 | 52 | public ScrollerLayout(Context context, AttributeSet attrs) { 53 | super(context, attrs); 54 | // 第一步,创建Scroller的实例 55 | mScroller = new Scroller(context); 56 | ViewConfiguration configuration = ViewConfiguration.get(context); 57 | // 获取TouchSlop值 58 | mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); 59 | } 60 | 61 | @Override 62 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 63 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 64 | int childCount = getChildCount(); 65 | for (int i = 0; i < childCount; i++) { 66 | View childView = getChildAt(i); 67 | // 为ScrollerLayout中的每一个子控件测量大小 68 | measureChild(childView, widthMeasureSpec, heightMeasureSpec); 69 | } 70 | } 71 | 72 | @Override 73 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 74 | if (changed) { 75 | int childCount = getChildCount(); 76 | for (int i = 0; i < childCount; i++) { 77 | View childView = getChildAt(i); 78 | // 为ScrollerLayout中的每一个子控件在水平方向上进行布局 79 | childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight()); 80 | } 81 | // 初始化左右边界值 82 | leftBorder = getChildAt(0).getLeft(); 83 | rightBorder = getChildAt(getChildCount() - 1).getRight(); 84 | } 85 | } 86 | 87 | @Override 88 | public boolean onInterceptTouchEvent(MotionEvent ev) { 89 | switch (ev.getAction()) { 90 | case MotionEvent.ACTION_DOWN: 91 | mXDown = ev.getRawX(); 92 | mXLastMove = mXDown; 93 | break; 94 | case MotionEvent.ACTION_MOVE: 95 | mXMove = ev.getRawX(); 96 | float diff = Math.abs(mXMove - mXDown); 97 | mXLastMove = mXMove; 98 | // 当手指拖动值大于TouchSlop值时,认为应该进行滚动,拦截子控件的事件 99 | if (diff > mTouchSlop) { 100 | return true; 101 | } 102 | break; 103 | } 104 | return super.onInterceptTouchEvent(ev); 105 | } 106 | 107 | @Override 108 | public boolean onTouchEvent(MotionEvent event) { 109 | switch (event.getAction()) { 110 | case MotionEvent.ACTION_MOVE: 111 | mXMove = event.getRawX(); 112 | int scrolledX = (int) (mXLastMove - mXMove); 113 | if (getScrollX() + scrolledX < leftBorder) { 114 | scrollTo(leftBorder, 0); 115 | return true; 116 | } else if (getScrollX() + getWidth() + scrolledX > rightBorder) { 117 | scrollTo(rightBorder - getWidth(), 0); 118 | return true; 119 | } 120 | scrollBy(scrolledX, 0); 121 | mXLastMove = mXMove; 122 | break; 123 | case MotionEvent.ACTION_UP: 124 | // 当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面 125 | int targetIndex = (getScrollX() + getWidth() / 2) / getWidth(); 126 | int dx = targetIndex * getWidth() - getScrollX(); 127 | // 第二步,调用startScroll()方法来初始化滚动数据并刷新界面 128 | mScroller.startScroll(getScrollX(), 0, dx, 0); 129 | invalidate(); 130 | break; 131 | } 132 | return super.onTouchEvent(event); 133 | } 134 | 135 | @Override 136 | public void computeScroll() { 137 | // 第三步,重写computeScroll()方法,并在其内部完成平滑滚动的逻辑 138 | if (mScroller.computeScrollOffset()) { 139 | scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 140 | invalidate(); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/customview/sample_1/ScrollerSample_1.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.customview.sample_1 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.widget.Button 7 | import android.widget.LinearLayout 8 | import android.widget.Scroller 9 | 10 | /** 11 | *
12 | * author : devyk on 2019-11-16 13:47 13 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 14 | * github : https://github.com/yangkun19921001 15 | * mailbox : yang1001yk@gmail.com 16 | * desc : This is ScrollerSample_1 17 | *18 | */ 19 | 20 | class ScrollerSample_1 : LinearLayout { 21 | 22 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 23 | 24 | constructor(context: Context) : super(context) 25 | 26 | /** 27 | * 定义滑动 Scroller 28 | */ 29 | private val mScroller = Scroller(context) 30 | 31 | 32 | public fun smoothScrollTo(destX: Int = -100, destY: Int = -100) { 33 | //滑动了的位置 34 | val scrollX = scrollY; 35 | val delta = destY - scrollY; 36 | //2000 ms 内滑动到 destX 位置,效果就是缓慢滑动 37 | mScroller.startScroll(scrollX, 0, 0, delta, 2000) 38 | invalidate() 39 | } 40 | 41 | override fun computeScroll() { 42 | if (mScroller.computeScrollOffset()) { 43 | scrollTo(mScroller.currX, mScroller.currY) 44 | postInvalidate() 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/customview/sample_1/SlideView1.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.customview.sample_1 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.view.MotionEvent 9 | import android.view.View 10 | 11 | /** 12 | *
13 | * author : devyk on 2019-11-16 18:01 14 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 15 | * github : https://github.com/yangkun19921001 16 | * mailbox : yang1001yk@gmail.com 17 | * desc : This is SlideView1 18 | *19 | */ 20 | public class SlideView1(context: Context?, attrs: AttributeSet?) : View(context, attrs) { 21 | 22 | /** 23 | * 记录上次滑动的坐标 24 | */ 25 | private var mLastX = 0; 26 | private var mLastY = 0; 27 | 28 | /** 29 | * 初始化画笔 30 | */ 31 | val paint = Paint().apply { 32 | color = Color.BLACK 33 | isAntiAlias = true 34 | strokeWidth = 3f 35 | } 36 | 37 | 38 | override fun onTouchEvent(event: MotionEvent): Boolean { 39 | when (event.action) { 40 | MotionEvent.ACTION_DOWN -> { 41 | //拿到相对于屏幕按下的坐标点 42 | mLastX = event.getX().toInt(); 43 | mLastY = event.getY().toInt(); 44 | println("拿到相对于屏幕按下的坐标点: x:$mLastX y:$mLastY") 45 | 46 | } 47 | MotionEvent.ACTION_MOVE -> { 48 | var offsetX = event.getX().toInt() - mLastX;//计算 View 新的摆放位置 49 | var offsetY = event.getY().toInt() - mLastY; 50 | //重新放置新的位置 51 | layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY); 52 | } 53 | 54 | MotionEvent.ACTION_UP -> { 55 | 56 | } 57 | } 58 | return true//消耗触摸事件 59 | } 60 | 61 | override fun onDraw(canvas: Canvas) { 62 | super.onDraw(canvas) 63 | canvas.drawCircle(300f, 300f, 150f, paint) 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/customview/sample_1/SlideView2.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.customview.sample_1 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.view.MotionEvent 9 | import android.view.View 10 | import android.widget.Scroller 11 | 12 | /** 13 | *
14 | * author : devyk on 2019-11-16 18:38 15 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 16 | * github : https://github.com/yangkun19921001 17 | * mailbox : yang1001yk@gmail.com 18 | * desc : This is SlideView2 19 | *20 | */ 21 | 22 | public class SlideView2(context: Context?, attrs: AttributeSet?) : View(context, attrs) { 23 | 24 | /** 25 | * 记录上次滑动的坐标 26 | */ 27 | private var mLastX = 0; 28 | private var mLastY = 0; 29 | 30 | private val mScroller = Scroller(context) 31 | 32 | /** 33 | * 初始化画笔 34 | */ 35 | val paint = Paint().apply { 36 | color = Color.BLACK 37 | isAntiAlias = true 38 | strokeWidth = 3f 39 | } 40 | 41 | 42 | override fun onTouchEvent(event: MotionEvent): Boolean { 43 | when (event.action) { 44 | MotionEvent.ACTION_DOWN -> { 45 | //拿到相对于屏幕按下的坐标点 46 | mLastX = event.getRawX().toInt(); 47 | mLastY = event.getRawY().toInt(); 48 | println("拿到相对于屏幕按下的坐标点1: x:$mLastX y:$mLastY") 49 | 50 | } 51 | MotionEvent.ACTION_MOVE -> { 52 | println("拿到相对于屏幕按下的坐标点2: x:${event.rawX} y:${event.getRawY()}") 53 | x = event.getRawX() - mLastX 54 | y = event.getRawY() - mLastY 55 | 56 | 57 | translationX = event.getRawX() - mLastX 58 | translationY = event.getRawY() - mLastY 59 | println("拿到相对于屏幕按下的坐标点3: x:${x} y:${y}") 60 | } 61 | 62 | MotionEvent.ACTION_UP -> { 63 | 64 | } 65 | } 66 | return true//消耗触摸事件 67 | } 68 | 69 | override fun onDraw(canvas: Canvas) { 70 | super.onDraw(canvas) 71 | canvas.drawCircle(300f, 300f, 150f, paint) 72 | } 73 | } -------------------------------------------------------------------------------- /app/src/main/res/animator/anim_infinite.xml: -------------------------------------------------------------------------------- 1 | 2 |
11 | * author : devyk on 2019-12-01 13:58 12 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 13 | * github : https://github.com/yangkun19921001 14 | * mailbox : yang1001yk@gmail.com 15 | * desc : This is BitmapUtis 16 | *17 | */ 18 | public class BitmapUtis 19 | { 20 | 21 | companion object{ 22 | public fun changeBitmapSize(context: Context,res: Int,newWidth : Int = 500,newHeight:Int = 500) : Bitmap { 23 | var bitmap = BitmapFactory.decodeResource(context.getResources(), res); 24 | var width = bitmap.getWidth(); 25 | var height = bitmap.getHeight(); 26 | Log.e("width","width:"+width); 27 | Log.e("height","height:"+height); 28 | 29 | //计算压缩的比率 30 | var scaleWidth=(newWidth)/width .toFloat() 31 | var scaleHeight=(newHeight)/height.toFloat(); 32 | 33 | //获取想要缩放的matrix 34 | var matrix = Matrix(); 35 | matrix.postScale(scaleWidth,scaleHeight.toFloat()); 36 | 37 | //获取新的bitmap 38 | bitmap= Bitmap.createBitmap(bitmap,0,0,width,height,matrix,true); 39 | bitmap.getWidth(); 40 | bitmap.getHeight(); 41 | Log.e("newWidth","newWidth"+bitmap.getWidth()); 42 | Log.e("newHeight","newHeight"+bitmap.getHeight()); 43 | return bitmap; 44 | } 45 | } 46 | 47 | 48 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/base/BaseView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.base 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.util.AttributeSet 6 | import android.view.View 7 | import android.view.WindowManager 8 | import com.devyk.custom_view.utils.HelpDraw 9 | import com.devyk.custom_view.utils.Utils 10 | 11 | /** 12 | *
13 | * author : devyk on 2019-12-01 20:13 14 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 15 | * github : https://github.com/yangkun19921001 16 | * mailbox : yang1001yk@gmail.com 17 | * desc : This is BaseView 18 | *19 | */ 20 | open class BaseView : View { 21 | private var mGridPaint: Paint? = null//网格画笔 22 | private var mWinSize: Point? = null//屏幕尺寸 23 | private var mCoo: Point? = null//坐标系原点 24 | 25 | 26 | public var mViewWidth: Int = 0 27 | public var mViewHeight: Int = 0 28 | 29 | public var mTempPath = Path() 30 | public var mTempPaint = Paint(Paint.ANTI_ALIAS_FLAG) 31 | 32 | 33 | 34 | /** 35 | * 基础 Paint 36 | */ 37 | public var mPaint = Paint(Paint.ANTI_ALIAS_FLAG) 38 | 39 | /** 40 | * 坐标系中心点 41 | */ 42 | private val mX = 0 43 | private val mY = 0 44 | 45 | 46 | constructor(context: Context?) : super(context) { 47 | init(context, null) 48 | } 49 | 50 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { 51 | init(context, attrs) 52 | } 53 | 54 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { 55 | init(context, attrs) 56 | } 57 | 58 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { 59 | super.onSizeChanged(w, h, oldw, oldh) 60 | mViewWidth = w 61 | mViewHeight = h 62 | } 63 | 64 | public open fun init(context: Context?, attrs: AttributeSet?) { 65 | //准备屏幕尺寸 66 | mWinSize = Point() 67 | mCoo = Point(mX, mY) 68 | Utils.loadWinSize(getContext(), mWinSize) 69 | mGridPaint = Paint(Paint.ANTI_ALIAS_FLAG) 70 | 71 | mTempPaint.color = Color.RED 72 | mTempPaint.strokeWidth = 5f 73 | 74 | } 75 | 76 | override fun onDraw(canvas: Canvas?) { 77 | super.onDraw(canvas) 78 | HelpDraw.drawGrid(canvas, mWinSize, mGridPaint) 79 | HelpDraw.drawCoo(canvas, mCoo, mWinSize, mGridPaint) 80 | } 81 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/canvas/CircleView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.canvas 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 com.devyk.custom_view.base.BaseView 9 | 10 | /** 11 | *
12 | * author : devyk on 2019-12-01 20:18 13 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 14 | * github : https://github.com/yangkun19921001 15 | * mailbox : yang1001yk@gmail.com 16 | * desc : This is CircleView 17 | *18 | */ 19 | class CircleView(context: Context?) : BaseView(context) { 20 | 21 | 22 | 23 | 24 | 25 | 26 | /** 27 | * 初始化 28 | */ 29 | override fun init(context: Context?, attrs: AttributeSet?) { 30 | super.init(context, attrs) 31 | 32 | 33 | mPaint = Paint(Paint.ANTI_ALIAS_FLAG) 34 | mPaint.setColor(Color.RED) 35 | mPaint.strokeCap = Paint.Cap.ROUND 36 | mPaint.strokeWidth = 5f 37 | 38 | 39 | 40 | } 41 | 42 | override fun draw(canvas: Canvas) { 43 | super.draw(canvas) 44 | //绘制一个圆 45 | canvas.drawCircle(500f, 500f, 150f, mPaint) 46 | mPaint.setColor(Color.WHITE) 47 | mPaint.strokeWidth = 10f 48 | //绘制一个点 49 | canvas.drawPoint(500f, 500f, mPaint) 50 | mPaint.setColor(Color.BLACK) 51 | //绘制一条线 52 | canvas.drawLine(500f,500f,650f,500f,mPaint) 53 | 54 | 55 | 56 | } 57 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/canvas/CustomDrawable.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.canvas 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.ColorFilter 5 | import android.graphics.Rect 6 | import android.graphics.drawable.Drawable 7 | import android.util.Log 8 | import android.view.Gravity 9 | 10 | /** 11 | *
12 | * author : devyk on 2019-12-02 18:23 13 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 14 | * github : https://github.com/yangkun19921001 15 | * mailbox : yang1001yk@gmail.com 16 | * desc : This is CustomDrawable 17 | *18 | */ 19 | 20 | class CustomDrawable( 21 | private val mUnselectedDrawable: Drawable, 22 | private val mSelectedDrawable: Drawable, 23 | private val mOrientation: Int 24 | ) : 25 | Drawable() { 26 | private val mTmpRect = Rect() 27 | 28 | override fun draw(canvas: Canvas) { 29 | // 绘制 30 | val level = level//from 0 (minimum) to 10000 31 | Log.d(TAG, "level=$level") 32 | //三个区间 33 | //右边区间和左边区间--设置成灰色 34 | if (level == 10000 || level == 0) { 35 | mUnselectedDrawable.draw(canvas) 36 | } else if (level == 5000) {//全部选中--设置成彩色 37 | mSelectedDrawable.draw(canvas) 38 | } else { 39 | //混合效果的Drawable 40 | /** 41 | * 将画板切割成两块-左边和右边 42 | */ 43 | val r = mTmpRect 44 | //得到当前自身 Drawable 的矩形区域 45 | val bounds = bounds 46 | run { 47 | //1.先绘制灰色部分 48 | //level 0~5000~10000 49 | //比例 50 | //4680 / 5000 -1f 51 | val ratio = level / 5000f - 1f 52 | var w = bounds.width() 53 | if (mOrientation == HORIZONTAL) { 54 | //我们要扣的宽度 55 | w = (w * Math.abs(ratio)).toInt() 56 | Log.d(TAG, "w=$w") 57 | } 58 | var h = bounds.height() 59 | if (mOrientation == VERTICAL) { 60 | h = (h * Math.abs(ratio)).toInt() 61 | } 62 | 63 | val gravity = if (ratio < 0) Gravity.LEFT else Gravity.RIGHT 64 | //从一个已有的bounds矩形边界范围中抠出一个矩形r 65 | Gravity.apply( 66 | gravity, //从左边还是右边开始抠 67 | w, //目标矩形的宽 68 | h, //目标矩形的高 69 | bounds, //被抠出来的rect 70 | r 71 | )//目标rect 72 | 73 | canvas.save()//保存画布 74 | canvas.clipRect(r)//切割 75 | mUnselectedDrawable.draw(canvas)//画 76 | canvas.restore()//恢复之前保存的画布 77 | } 78 | run { 79 | //2.再绘制彩色部分 80 | //level 0~5000~10000 81 | //比例 82 | val ratio = level / 5000f - 1f 83 | var w = bounds.width() 84 | if (mOrientation == HORIZONTAL) { 85 | w -= (w * Math.abs(ratio)).toInt() 86 | } 87 | var h = bounds.height() 88 | if (mOrientation == VERTICAL) { 89 | h -= (h * Math.abs(ratio)).toInt() 90 | } 91 | 92 | val gravity = if (ratio < 0) Gravity.RIGHT else Gravity.LEFT 93 | //从一个已有的bounds矩形边界范围中抠出一个矩形r 94 | Gravity.apply( 95 | gravity, //从左边还是右边开始抠 96 | w, //目标矩形的宽 97 | h, //目标矩形的高 98 | bounds, //被抠出来的rect 99 | r 100 | )//目标rect 101 | 102 | canvas.save()//保存画布 103 | canvas.clipRect(r)//切割 104 | mSelectedDrawable.draw(canvas)//画 105 | canvas.restore()//恢复之前保存的画布 106 | } 107 | 108 | } 109 | 110 | } 111 | 112 | override fun onBoundsChange(bounds: Rect) { 113 | // 定好两个Drawable图片的宽高---边界bounds 114 | mUnselectedDrawable.bounds = bounds 115 | mSelectedDrawable.bounds = bounds 116 | Log.d(TAG, "onBoundsChange w = " + bounds.width()) 117 | } 118 | 119 | override fun getIntrinsicWidth(): Int { 120 | //得到Drawable的实际宽度 121 | return Math.max( 122 | mSelectedDrawable.intrinsicWidth, 123 | mUnselectedDrawable.intrinsicWidth 124 | ) 125 | } 126 | 127 | override fun getIntrinsicHeight(): Int { 128 | //得到Drawable的实际高度 129 | return Math.max( 130 | mSelectedDrawable.intrinsicHeight, 131 | mUnselectedDrawable.intrinsicHeight 132 | ) 133 | } 134 | 135 | override fun onLevelChange(level: Int): Boolean { 136 | // 当设置level的时候回调---提醒自己重新绘制 137 | invalidateSelf() 138 | return true 139 | } 140 | 141 | override fun setAlpha(i: Int) { 142 | 143 | } 144 | 145 | override fun setColorFilter(colorFilter: ColorFilter?) { 146 | 147 | } 148 | 149 | override fun getOpacity(): Int { 150 | return 0 151 | } 152 | 153 | companion object { 154 | 155 | private val TAG = this.javaClass.simpleName 156 | val HORIZONTAL = 1 157 | val VERTICAL = 2 158 | } 159 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/canvas/GallaryHorizonalScrollView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.canvas 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.Drawable 5 | import android.util.AttributeSet 6 | import android.util.Log 7 | import android.view.MotionEvent 8 | import android.view.View 9 | import android.widget.HorizontalScrollView 10 | import android.widget.ImageView 11 | import android.widget.LinearLayout 12 | 13 | /** 14 | *
15 | * author : devyk on 2019-12-02 18:23 16 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 17 | * github : https://github.com/yangkun19921001 18 | * mailbox : yang1001yk@gmail.com 19 | * desc : This is GallaryHorizonalScrollView 20 | *21 | */ 22 | 23 | class GallaryHorizonalScrollView : HorizontalScrollView, View.OnTouchListener { 24 | private var container: LinearLayout? = null 25 | private var centerX: Int = 0 26 | private var icon_width: Int = 0 27 | 28 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { 29 | init() 30 | } 31 | 32 | constructor(context: Context) : super(context) { 33 | init() 34 | } 35 | 36 | private fun init() { 37 | //在ScrollView里面放置一个水平线性布局,再往里面放置很多ImageView 38 | val params = LinearLayout.LayoutParams( 39 | LinearLayout.LayoutParams.WRAP_CONTENT, 40 | LinearLayout.LayoutParams.WRAP_CONTENT 41 | ) 42 | container = LinearLayout(context) 43 | container!!.layoutParams = params 44 | setOnTouchListener(this) 45 | } 46 | 47 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { 48 | super.onLayout(changed, l, t, r, b) 49 | //得到某一张图片的宽度 50 | val v = container!!.getChildAt(0) 51 | icon_width = v.width 52 | Log.d(TAG, "icon_width = $icon_width") 53 | //得到hzv的中间x坐标 54 | centerX = width / 2 55 | Log.d(TAG, "centerX = $centerX") 56 | //处理下,中心坐标改为中心图片的左边界 57 | centerX = centerX - icon_width / 2 58 | //给LinearLayout和hzv之间设置边框距离 59 | container!!.setPadding(centerX, 0, centerX, 0) 60 | } 61 | 62 | override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { 63 | super.onScrollChanged(l, t, oldl, oldt) 64 | Log.e(TAG, "onScrollChanged") 65 | //渐变图片 66 | reveal() 67 | } 68 | 69 | private fun reveal() { 70 | // 渐变效果 71 | //得到hzv滑出去的距离 72 | val scrollX = scrollX 73 | Log.d(TAG, scrollX.toString() + "") 74 | //找到两张渐变的图片的下标--左,右 75 | val index_left = scrollX / icon_width 76 | Log.d(TAG, index_left.toString() + "index_left") 77 | val index_right = index_left + 1 78 | //设置图片的level 79 | for (i in 0 until container!!.childCount) { 80 | if (i == index_left || i == index_right) { 81 | //变化 82 | //比例: 83 | 84 | val ratio = 5000f / icon_width 85 | val iv_left = container!!.getChildAt(index_left) as ImageView 86 | //scrollX%icon_width:代表滑出去的距离 87 | //滑出去了icon_width/2 icon_width/2%icon_width 88 | iv_left.setImageLevel( 89 | //代表的是,我滑动之后的距离在5000份当中的份额 90 | (5000 - scrollX % icon_width * ratio).toInt() 91 | ) 92 | //右边 93 | if (index_right < container!!.childCount) { 94 | val iv_right = container!!.getChildAt(index_right) as ImageView 95 | //scrollX%icon_width:代表滑出去的距离 96 | //滑出去了icon_width/2 icon_width/2%icon_width 97 | iv_right.setImageLevel( 98 | (10000 - scrollX % icon_width * ratio).toInt() 99 | ) 100 | } 101 | } else { 102 | //灰色 103 | val iv = container!!.getChildAt(i) as ImageView 104 | iv.setImageLevel(0) 105 | } 106 | } 107 | } 108 | 109 | //添加图片的方法 110 | fun addImageViews(revealDrawables: Array
15 | * author : devyk on 2019-12-03 16:11 16 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 17 | * github : https://github.com/yangkun19921001 18 | * mailbox : yang1001yk@gmail.com 19 | * desc : This is PathView 20 | *21 | */ 22 | public class PathView : BaseView { 23 | constructor(context: Context?) : super(context) 24 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 25 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) 26 | 27 | 28 | lateinit var mPathPaint: Paint 29 | 30 | lateinit var mPath: Path 31 | 32 | var region = Region() 33 | 34 | override fun init(context: Context?, attrs: AttributeSet?) { 35 | super.init(context, attrs) 36 | mPathPaint = Paint(Paint.ANTI_ALIAS_FLAG) 37 | mPathPaint.style = Paint.Style.STROKE 38 | mPathPaint.strokeWidth = 5f 39 | mPathPaint.setColor(Color.RED) 40 | 41 | mPath = Path() 42 | 43 | /** 44 | * 1. reset, rewind (清除Path中的内容 reset 不保留内部数据结构,但会保留 FillType. rewind 会保留内部的数据结构,但不保留FillType) 45 | * 2. lineTo: 46 | * 47 | * 48 | * 49 | * 50 | */ 51 | 52 | } 53 | 54 | 55 | /** 56 | * 开始绘制 57 | */ 58 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 59 | override fun onDraw(canvas: Canvas?) { 60 | super.onDraw(canvas) 61 | //清除Path中的内容 reset 不保留内部数据结构,但会保留 FillType 62 | mPath.reset() 63 | 64 | /** 65 | * 1. setLastPoint 66 | */ 67 | 68 | /* //从0.0 连接 400,600 69 | mPath.lineTo(400f,600f) 70 | //重置上一点相当于 0,0 到 600,200, 设置之前操作的最后一个点位置(会影响之前跟之后的起始点) 71 | mPath.setLastPoint(600f,200f) 72 | //启动为 600,200 启动 73 | mPath.lineTo(900f,100f)*/ 74 | 75 | 76 | /**2.moveTo*/ 77 | 78 | /* //moveTo 设置起点 79 | mPath.moveTo(600f,200f) 80 | //从0.0 连接 400,600 81 | mPath.lineTo(400f,600f) 82 | mPath.lineTo(800f,300f) 83 | //最后一点和起点封闭 84 | mPath.close()*/ 85 | 86 | /** 87 | * 3. close 88 | */ 89 | /* 90 | //从0.0 连接 400,600 91 | mPath.lineTo(400f,600f) 92 | //moveTo 设置起点 93 | // mPath.moveTo(600f,200f) 94 | //启动为 600,200 启动 95 | mPath.lineTo(900f,100f) 96 | //最后一点和起点封闭 97 | mPath.close()*/ 98 | 99 | /** 100 | * 4.addRect 101 | * @param CW -> 顺时针 102 | * @param CCW -> 逆时针 103 | */ 104 | // mPath.addRect(400f,400f,800f,800f,Path.Direction.CW) 105 | 106 | 107 | mPath.addCircle(500f, 500f, 150f, Path.Direction.CW) 108 | 109 | 110 | canvas!!.drawPath(mPath, mPathPaint) // 绘制Path 111 | 112 | 113 | canvas.drawRect(300f,800f,800f,1300f ,mPathPaint) // 绘制边界 114 | 115 | 116 | // mPath.moveTo(100f,900f) 117 | // mPath.cubicTo(100f,900f,(800-100)/2f,900 + 900/2f,800f,900f) 118 | 119 | 120 | 121 | 122 | /* mPathPaint.textSize = 50f 123 | //1. 添加矩形到 Path 124 | mPath.addRect(100f,300f,400f,700f,Path.Direction.CW)//顺时针 125 | canvas!!.drawText("1",200f,500f,mPathPaint) 126 | 127 | //2. 添加 圆角矩形到 Path 128 | mPath.addRoundRect(100f + 500,300f,1000f ,700f,30f,30f,Path.Direction.CCW)//逆时针 129 | canvas!!.drawText("2",800f,500f,mPathPaint) 130 | 131 | //3. 添加 椭圆 到 Path 132 | mPath.addOval(100f,1300f,600f ,1000f,Path.Direction.CCW)//逆时针 133 | canvas!!.drawText("3",300f,1150f,mPathPaint) 134 | 135 | //4. 添加 圆 到 Path 136 | mPath.addCircle(850f,1200f ,150f,Path.Direction.CCW)//逆时针 137 | canvas!!.drawText("4",850f,1200f,mPathPaint) 138 | 139 | //5. 添加 圆弧 到 Path ,直接添加一个圆弧到path中 140 | //添加一个圆弧到 path,如果圆弧的起点和上次最后一个坐标点不相同,就连接两个点 141 | mPath.addArc(100f,1500f,600f,1800f,0f,300f) 142 | canvas!!.drawText("5",300f,1550f,mPathPaint) 143 | 144 | mPath.arcTo(650f,1500f,800f,1800f,0f,180f,true) 145 | canvas!!.drawText("6",750f,1550f,mPathPaint) 146 | 147 | canvas!!.drawPath(mPath, mPathPaint)*/ 148 | } 149 | 150 | 151 | override fun onTouchEvent(event: MotionEvent): Boolean { 152 | when (event.action) { 153 | MotionEvent.ACTION_DOWN -> return true 154 | MotionEvent.ACTION_UP -> { 155 | val rectF = RectF() 156 | mPath.computeBounds(rectF, true) 157 | region.setPath( 158 | mPath, 159 | Region(rectF.left.toInt(), rectF.top.toInt(), rectF.right.toInt(), rectF.bottom.toInt()) 160 | ) 161 | 162 | 163 | if (region.contains(event.x.toInt(), event.y.toInt())) { 164 | Toast.makeText(context, "点击了圆", Toast.LENGTH_SHORT).show() 165 | } 166 | 167 | region.set(300, 800, 800, 1300) 168 | if (region.contains(event.x.toInt(), event.y.toInt())) { 169 | Toast.makeText(context, "点击了矩形", Toast.LENGTH_SHORT).show() 170 | } 171 | } 172 | } 173 | return super.onTouchEvent(event) 174 | } 175 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/canvas/path_measure/CarRotate.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.canvas.path_measure 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.util.AttributeSet 6 | import com.devyk.custom_view.R 7 | import com.devyk.custom_view.base.BaseView 8 | 9 | /** 10 | *
11 | * author : devyk on 2019-12-04 10:50 12 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 13 | * github : https://github.com/yangkun19921001 14 | * mailbox : yang1001yk@gmail.com 15 | * desc : This is AirplaneRotate 小飞机旋转 16 | *17 | */ 18 | class CarRotate : BaseView { 19 | 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(context, attrs, defStyleAttr) 24 | 25 | /** 26 | * 定义一个 Bitmap 27 | */ 28 | var mBitmap: Bitmap? = null 29 | 30 | /** 31 | * 定义一个 path 测量 32 | */ 33 | var mPathMeasure: PathMeasure? = null 34 | 35 | /** 36 | * 定义一个矩阵,目的是给 bitmap 修改角度 37 | */ 38 | var mMatrix: Matrix? = null 39 | 40 | /** 41 | * 截取的变量值 42 | */ 43 | var mCurValues = 0f 44 | 45 | private var pos: FloatArray? = null // 当前点的实际位置 46 | private var tan: FloatArray? = null // 当前点的tangent值,用于计算图片所需旋转的角度 47 | 48 | override fun init(context: Context?, attrs: AttributeSet?) { 49 | super.init(context, attrs) 50 | 51 | //初始化 bitmap 52 | val options = BitmapFactory.Options() 53 | options.inSampleSize = 8 54 | mBitmap = BitmapFactory.decodeResource(context!!.resources, R.drawable.car, options) 55 | mPathMeasure = PathMeasure() 56 | mMatrix = Matrix() 57 | 58 | pos = FloatArray(2) 59 | tan = FloatArray(2) 60 | 61 | mTempPaint.style = Paint.Style.STROKE 62 | 63 | } 64 | 65 | 66 | /** 67 | * 实现 1 68 | */ 69 | /* override fun onDraw(canvas: Canvas?) { 70 | super.onDraw(canvas) 71 | 72 | //清楚 path 数据 73 | mTempPath.rewind() 74 | //绘制一个模拟公路 75 | addLineToPath() 76 | //测量 path,不闭合 77 | mPathMeasure!!.setPath(mTempPath, true) 78 | //动态变化的值 79 | mCurValues += 0.002f 80 | if (mCurValues >= 1) mCurValues = 0f 81 | //拿到当前点上的 正弦值 82 | mPathMeasure!!.getPosTan(mPathMeasure!!.length * mCurValues, pos, tan) 83 | //通过正弦值拿到当前弧度 84 | val y = tan!![1].toDouble() 85 | val x = tan!![0].toDouble() 86 | //拿到 bitmap 需要旋转的角度,之后将矩阵旋转 87 | var degrees = (Math.atan2(y, x) * 180f / Math.PI).toFloat() 88 | println("角度:$degrees") 89 | mMatrix!!.reset() 90 | mMatrix!!.postRotate(degrees, mBitmap!!.width / 2.toFloat(), mBitmap!!.height / 2.toFloat()) 91 | mMatrix!!.postTranslate(pos!![0] - mBitmap!!.getWidth() / 2, pos!![1] - mBitmap!!.getHeight() / 2) 92 | //绘制Bitmap和path 93 | canvas!!.drawPath(mTempPath, mTempPaint) 94 | canvas!!.drawBitmap(mBitmap!!, mMatrix!!, mTempPaint) 95 | 96 | //重绘 97 | postInvalidate() 98 | }*/ 99 | 100 | /** 101 | * 实现 2 102 | */ 103 | override fun onDraw(canvas: Canvas?) { 104 | super.onDraw(canvas) 105 | 106 | //清楚 path 数据 107 | mTempPath.rewind() 108 | //绘制一个模拟公路 109 | addLineToPath() 110 | //测量 path,不闭合 111 | mPathMeasure!!.setPath(mTempPath, true) 112 | //动态变化的值 113 | mCurValues += 0.002f 114 | if (mCurValues >= 1) mCurValues = 0f 115 | 116 | 117 | // 获取当前位置的坐标以及趋势的矩阵 118 | mPathMeasure!!.getMatrix(mPathMeasure!!.getLength() * mCurValues, mMatrix!!, 119 | (PathMeasure.TANGENT_MATRIX_FLAG or PathMeasure.POSITION_MATRIX_FLAG)) 120 | // 将图片绘制中心调整到与当前点重合(偏移加旋转) 121 | mMatrix!!.preTranslate(-mBitmap!!.getWidth() / 2f, -mBitmap!!.getHeight() / 2f); 122 | 123 | 124 | 125 | //绘制Bitmap和path 126 | canvas!!.drawPath(mTempPath, mTempPaint) 127 | canvas!!.drawBitmap(mBitmap!!, mMatrix!!, mTempPaint) 128 | 129 | //重绘 130 | postInvalidate() 131 | 132 | } 133 | 134 | private fun addLineToPath() { 135 | mTempPath.moveTo(100f, 100f) 136 | mTempPath.lineTo(100f, 200f) 137 | mTempPath.lineTo(200f, 300f) 138 | mTempPath.lineTo(300f, 400f) 139 | mTempPath.lineTo(400f, 500f) 140 | mTempPath.lineTo(500f, 600f) 141 | mTempPath.lineTo(600f, 300f) 142 | mTempPath.lineTo(600f, 900f) 143 | mTempPath.lineTo(900f, 1200f) 144 | mTempPath.lineTo(1200f, 800f) 145 | mTempPath.lineTo(800f, 900f) 146 | mTempPath.lineTo(900f, 100f) 147 | mTempPath.close() 148 | } 149 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/canvas/path_measure/PathMeasureView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.canvas.path_measure 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 com.devyk.custom_view.base.BaseView 10 | 11 | /** 12 | *
13 | * author : devyk on 2019-12-03 22:00 14 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 15 | * github : https://github.com/yangkun19921001 16 | * mailbox : yang1001yk@gmail.com 17 | * desc : This is PathMeasureView 18 | *19 | */ 20 | class PathMeasureView : BaseView { 21 | 22 | constructor(context: Context?) : super(context) 23 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 24 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) 25 | 26 | lateinit var mPathPaint: Paint 27 | 28 | lateinit var mPath: Path 29 | lateinit var mTempPath2: Path 30 | 31 | lateinit var mPathMeasure: PathMeasure 32 | lateinit var mPathMeasure2: PathMeasure 33 | 34 | var stop = 0f 35 | var stopValues = 0f 36 | 37 | 38 | override fun init(context: Context?, attrs: AttributeSet?) { 39 | super.init(context, attrs) 40 | mPathPaint = Paint(Paint.ANTI_ALIAS_FLAG) 41 | mPathPaint.style = Paint.Style.STROKE 42 | mPathPaint.strokeWidth = 5f 43 | mPathPaint.setColor(Color.RED) 44 | 45 | mPath = Path() 46 | mTempPath = Path() 47 | mTempPath2 = Path() 48 | 49 | mViewHeight 50 | 51 | 52 | mPathMeasure = PathMeasure() 53 | mPathMeasure2 = PathMeasure() 54 | 55 | setLayerType(LAYER_TYPE_SOFTWARE,null) 56 | 57 | val valueAnimator = ValueAnimator.ofFloat(0f, 1f) 58 | valueAnimator.addUpdateListener { 59 | animation -> stopValues = animation.animatedValue as Float 60 | invalidate() 61 | } 62 | valueAnimator.repeatCount = ValueAnimator.INFINITE 63 | valueAnimator.setDuration(1500) 64 | valueAnimator.start() 65 | 66 | 67 | } 68 | 69 | override fun draw(canvas: Canvas) { 70 | super.draw(canvas) 71 | 72 | /** 73 | * 1. getLength 74 | */ 75 | /* //将起点移动到 100,100 的位置 76 | mPath.moveTo(100f,100f) 77 | //绘制连接线 78 | mPath.lineTo(100f,450f) 79 | mPath.lineTo(450f,500f) 80 | mPath.lineTo(500f,100f) 81 | 82 | 83 | mPathMeasure.setPath(mPath,false)//不被闭合 84 | mPathMeasure2.setPath(mPath,true)//闭合 85 | 86 | println("forceClosed false pathLength =${mPathMeasure.length}") 87 | println("forceClosed true pathLength =${mPathMeasure2.length}")*/ 88 | 89 | 90 | /** 91 | * 2. nextContour 92 | */ 93 | 94 | /* mPath.addCircle(500f,500f,10f,Path.Direction.CW) 95 | mPath.addCircle(500f,500f,80f,Path.Direction.CW) 96 | mPath.addCircle(500f,500f,150f,Path.Direction.CW) 97 | mPath.addCircle(500f,500f,200f,Path.Direction.CW) 98 | 99 | mPathMeasure.setPath(mPath,false)//不被闭合 100 | canvas.drawPath(mPath,mPathPaint) 101 | 102 | do { 103 | println("forceClosed pathLength =${mPathMeasure.length}") 104 | }while (mPathMeasure.nextContour()) 105 | */ 106 | 107 | 108 | /** 109 | * 3. getSegment 110 | */ 111 | mPath.addCircle(500f,500f,200f,Path.Direction.CCW) 112 | mPathMeasure.setPath(mPath,false)//不被闭合 113 | mTempPath.rewind() 114 | stop = mPathMeasure.length * stopValues 115 | val start = (stop - (0.5 - Math.abs(stopValues - 0.5)) * mPathMeasure.length).toFloat() 116 | val segment = mPathMeasure.getSegment(start, stop, mTempPath, true) 117 | println("总长度:${mPathMeasure.length} 是否截取成功:$segment + start:$start stop:$stop") 118 | canvas.drawPath(mTempPath,mPathPaint) 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | } 127 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/flow/TagAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.flow 2 | import android.util.Log 3 | import android.view.View 4 | import com.devyk.custom_view.flow.FlowLayout 5 | 6 | 7 | import java.util.ArrayList 8 | import java.util.Arrays 9 | import java.util.HashSet 10 | 11 | abstract class TagAdapter< T> { 12 | private var mTagDatas: List
5 | * author : devyk on 2019-11-28 14:08 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is TagView 10 | *11 | */ 12 | 13 | 14 | import android.content.Context 15 | import android.view.View 16 | import android.widget.Checkable 17 | import android.widget.FrameLayout 18 | 19 | /** 20 | * Created by zhy on 15/9/10. 21 | */ 22 | class TagView(context: Context) : FrameLayout(context), Checkable { 23 | private var isChecked: Boolean = false 24 | 25 | val tagView: View 26 | get() = getChildAt(0) 27 | 28 | public override fun onCreateDrawableState(extraSpace: Int): IntArray { 29 | val states = super.onCreateDrawableState(extraSpace + 1) 30 | if (isChecked()) { 31 | View.mergeDrawableStates(states, CHECK_STATE) 32 | } 33 | return states 34 | } 35 | 36 | 37 | /** 38 | * Change the checked state of the view 39 | * 40 | * @param checked The new checked state 41 | */ 42 | override fun setChecked(checked: Boolean) { 43 | if (this.isChecked != checked) { 44 | this.isChecked = checked 45 | refreshDrawableState() 46 | } 47 | } 48 | 49 | /** 50 | * @return The current checked state of the view 51 | */ 52 | override fun isChecked(): Boolean { 53 | return isChecked 54 | } 55 | 56 | /** 57 | * Change the checked state of the view to the inverse of its current state 58 | */ 59 | override fun toggle() { 60 | setChecked(!isChecked) 61 | } 62 | 63 | companion object { 64 | private val CHECK_STATE = intArrayOf(android.R.attr.state_checked) 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/paint/FilterView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.paint 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.util.AttributeSet 6 | import android.view.View 7 | import com.devyk.custom_view.R 8 | 9 | /** 10 | *
11 | * author : devyk on 2019-11-30 20:50 12 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 13 | * github : https://github.com/yangkun19921001 14 | * mailbox : yang1001yk@gmail.com 15 | * desc : This is FilterView 16 | *17 | */ 18 | 19 | class FilterView : View { 20 | 21 | private var paint = Paint() 22 | 23 | lateinit var bitmap: Bitmap 24 | 25 | constructor(context: Context?) : super(context) 26 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 27 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) 28 | 29 | /** 30 | * 显示的高 31 | */ 32 | var showHeight = 0 33 | 34 | init { 35 | 36 | init() 37 | } 38 | 39 | private fun init() { 40 | 41 | paint = Paint(Paint.ANTI_ALIAS_FLAG) 42 | paint.color = Color.RED 43 | 44 | bitmap = BitmapFactory.decodeResource(resources, R.mipmap.gild_3) 45 | } 46 | 47 | override fun onDraw(canvas: Canvas) { 48 | super.onDraw(canvas) 49 | // //关闭单个View的硬件加速功 50 | // // setLayerType(View.LAYER_TYPE_SOFTWARE,null); 51 | 52 | 53 | 54 | 55 | //1. 缩放运算---乘法 -- 颜色增强 56 | val colorMartrix = ColorMatrix( 57 | floatArrayOf( 58 | 1.2f, 0f, 0f, 0f, 0f, 59 | 0f, 1.2f, 0f, 0f, 0f, 60 | 0f, 0f, 1.2f, 0f, 0f, 61 | 0f, 0f, 0f, 1.2f, 0f 62 | ) 63 | ) 64 | val rectF = RectF( 65 | 0f, 66 | showHeight.toFloat() , 67 | (bitmap.width / 2).toFloat(), 68 | (bitmap.height / 4).toFloat() 69 | ) 70 | drawFilterBitmap(colorMartrix, canvas,rectF) 71 | 72 | showHeight += bitmap.height / 4 73 | 74 | 75 | 76 | //2 平移运算---加法 77 | var colorMartrix2 = ColorMatrix(floatArrayOf( 78 | 1f, 0f,0f,0f,0f, 79 | 0f,1f,0f,0f,100f, 80 | 0f,0f,1f,0f,0f, 81 | 0f,0f,0f,1f,0f 82 | )) 83 | 84 | val rectF2 = RectF( 85 | 0f, 86 | showHeight.toFloat(), 87 | (bitmap.width / 2).toFloat(), 88 | (bitmap.height /4) * 2.toFloat() 89 | ) 90 | drawFilterBitmap(colorMartrix2, canvas,rectF2) 91 | 92 | 93 | showHeight += bitmap.height / 4 94 | 95 | 96 | 97 | //3. 反相效果 -- 底片效果 98 | var colorMartrix3 = ColorMatrix(floatArrayOf( 99 | -1f, 0f,0f,0f,255f, 100 | 0f,-1f,0f,0f,255f, 101 | 0f,0f,-1f,0f,255f, 102 | 0f,0f,0f,1f,0f 103 | )); 104 | 105 | val rectF3 = RectF( 106 | 0f, 107 | showHeight.toFloat(), 108 | (bitmap.width / 2).toFloat(), 109 | (bitmap.height /4) * 3.toFloat() 110 | ) 111 | drawFilterBitmap(colorMartrix3, canvas,rectF3) 112 | 113 | 114 | /** 115 | * 4.黑白照片 116 | * 是将我们的三通道变为单通道的灰度模式 117 | * 去色原理:只要把R G B 三通道的色彩信息设置成一样,那么图像就会变成灰色, 118 | * 同时为了保证图像亮度不变,同一个通道里的R+G+B =1 119 | */ 120 | var colorMartrix4 = ColorMatrix(floatArrayOf( 121 | 0.213f, 0.715f,0.072f,0f,0f, 122 | 0.213f, 0.715f,0.072f,0f,0f, 123 | 0.213f, 0.715f,0.072f,0f,0f, 124 | 0f,0f,0f,1f,0f 125 | )); 126 | 127 | 128 | showHeight += bitmap.height / 4 129 | val rectF4 = RectF( 130 | bitmap.width/2f, 131 | bitmap.height /2f, 132 | (bitmap.width).toFloat(), 133 | (bitmap.height /4) * 3.toFloat() 134 | ) 135 | drawFilterBitmap(colorMartrix4, canvas,rectF4) 136 | 137 | 138 | 139 | //5.发色效果---(比如红色和绿色交换) 140 | var colorMartrix5 = ColorMatrix(floatArrayOf( 141 | 1f,0f,0f,0f,0f, 142 | 0f, 0f,1f,0f,0f, 143 | 0f,1f,0f,0f,0f, 144 | 0f,0f,0f,0.5F,0f 145 | )); 146 | 147 | val rectF5 = RectF( 148 | bitmap.width / 2f, 149 | 0f, 150 | (bitmap.width / 2 * 2).toFloat(), 151 | (bitmap.height /4) .toFloat() 152 | ) 153 | drawFilterBitmap(colorMartrix5, canvas,rectF5) 154 | 155 | 156 | 157 | //6.复古效果 158 | var colorMartrix6= ColorMatrix(floatArrayOf( 159 | 1/2f,1/2f,1/2f,0f,0f, 160 | 1/3f, 1/3f,1/3f,0f,0f, 161 | 1/4f,1/4f,1/4f,0f,0f, 162 | 0f,0f,0f,1f,0f 163 | )); 164 | 165 | val rectF6 = RectF( 166 | bitmap.width / 2f, 167 | bitmap.height /4f, 168 | (bitmap.width / 2 * 2).toFloat(), 169 | (bitmap.height /4 * 2) .toFloat() 170 | ) 171 | drawFilterBitmap(colorMartrix6, canvas,rectF6) 172 | 173 | 174 | 175 | } 176 | 177 | private fun drawFilterBitmap(colorMartrix: ColorMatrix, canvas: Canvas,rectF: RectF) { 178 | 179 | paint.colorFilter = ColorMatrixColorFilter(colorMartrix) 180 | canvas.drawBitmap(bitmap, null, rectF, paint) 181 | } 182 | 183 | 184 | } 185 | -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/paint/LinearGradientTextView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.paint 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.util.AttributeSet 6 | import android.widget.TextView 7 | 8 | /** 9 | *
10 | * author : devyk on 2019-11-30 17:35 11 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 12 | * github : https://github.com/yangkun19921001 13 | * mailbox : yang1001yk@gmail.com 14 | * desc : This is LinearGradientTextView 15 | *16 | */ 17 | class LinearGradientTextView : TextView { 18 | 19 | /** 20 | * 定义线性渐变 21 | */ 22 | private var mLinearGradient: LinearGradient? = null 23 | 24 | /** 25 | * 定义一个矩阵 26 | */ 27 | private var mGradientatrix: Matrix? = null 28 | 29 | /** 30 | * 定义一个画笔 31 | */ 32 | private var mPaint: Paint? = null 33 | 34 | private var mViewWidth = 0 35 | private var mTranslate = 0 36 | 37 | private var delta = 15 38 | 39 | 40 | 41 | constructor(context: Context?) : super(context) 42 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 43 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) 44 | 45 | 46 | /** 47 | * 当字改变的时候回调 48 | */ 49 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { 50 | super.onSizeChanged(w, h, oldw, oldh) 51 | if (mViewWidth ==0){ 52 | //拿到当前 text 宽度 53 | mViewWidth = measuredWidth 54 | if (mViewWidth > 0){ 55 | //拿到当前画笔 56 | mPaint = paint 57 | //拿到 text 58 | var text = text.toString() 59 | //mViewWidth除字体总数就得到了每个字的像素 然后*3 表示3个文字的像素 60 | var size = 0; 61 | //如果当前 text 长度大于 0 62 | if (text.length > 0){ 63 | //拿到当前 3 个文字的像素 64 | size = mViewWidth / text.length * 3 65 | 66 | }else{//说明没有文字 67 | size = mViewWidth 68 | } 69 | /**线性渲染 70 | * x0, y0, 起始点 71 | * x1, y1, 结束点 72 | * int[] mColors, 中间依次要出现的几个颜色 73 | * float[] positions 位置数组,position的取值范围[0,1],作用是指定几个颜色分别放置在那个位置上, 74 | * 如果传null,渐变就线性变化。 75 | * tile 用于指定控件区域大于指定的渐变区域时,空白区域的颜色填充方法 76 | */ 77 | //从左边 size 开始,左边看不见的地方开始,以滚动扫描的形式过来 78 | mLinearGradient = LinearGradient(-size.toFloat(),0f,0f,0f, intArrayOf(0x33ffffff, -0x1, 0x33ffffff), 79 | floatArrayOf(0f, 0.2f, 1f), Shader.TileMode.CLAMP) 80 | //将线性渐变添加到 paint 中 81 | mPaint!!.setShader(mLinearGradient) 82 | //定义一个矩阵 83 | mGradientatrix = Matrix() 84 | } 85 | } 86 | } 87 | 88 | /** 89 | * 开始绘制 90 | */ 91 | override fun draw(canvas: Canvas?) { 92 | super.draw(canvas) 93 | val measureWindth = paint.measureText(text.toString()) 94 | mTranslate += delta 95 | /** 96 | * 如果位置已经移动到了边界,那么文字就开始往回滚动 97 | * 但是如果小于 1 那么又开始递增,执行另一个逻辑 98 | */ 99 | if (mTranslate > measureWindth + 1 || mTranslate < 1){ 100 | delta = -delta 101 | } 102 | 103 | //将矩阵平移 104 | mGradientatrix!!.setTranslate(mTranslate.toFloat(),0f) 105 | mLinearGradient!!.setLocalMatrix(mGradientatrix) 106 | //paint是textview的所以只需要不断色控制画笔的shader 然后利用矩阵控制位移即可 107 | postInvalidateDelayed(30) 108 | 109 | } 110 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/paint/RadarGradientView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.paint 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.util.AttributeSet 6 | import android.util.Log 7 | import android.view.View 8 | 9 | 10 | /** 11 | *
12 | * author : devyk on 2019-11-30 18:50 13 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 14 | * github : https://github.com/yangkun19921001 15 | * mailbox : yang1001yk@gmail.com 16 | * desc : This is RadarGradientView 渐变渲染/梯度渲染 17 | *18 | */ 19 | class RadarGradientView : View { 20 | 21 | 22 | private var mWidth: Int = 0 23 | private var mHeight: Int = 0 24 | 25 | private val TAG = javaClass.simpleName 26 | 27 | //五个圆 28 | private val pots = floatArrayOf(0.05f, 0.1f, 0.15f, 0.2f, 0.25f, 0.3f, 0.35f) 29 | 30 | private var scanShader: Shader? = null // 扫描渲染shader 31 | private val scanSpeed = 10 // 扫描速度 32 | private var scanAngle: Int = 0 // 扫描旋转的角度 33 | 34 | private lateinit var mMatrix: Matrix // 旋转需要的矩阵 35 | 36 | private var mPaintCircle = Paint() // 画圆用到的paint 37 | private var mPaintRadar = Paint() // 扫描用到的paint 38 | 39 | private val run = object : Runnable { 40 | override fun run() { 41 | scanAngle = (scanAngle + scanSpeed) % 125 // 42 | Log.d(TAG,"scanAngle:$scanAngle") 43 | mMatrix.postRotate(scanSpeed.toFloat(), (mWidth / 2).toFloat(), (mHeight / 2).toFloat()) // 旋转矩阵 44 | invalidate() // 通知view重绘 45 | postDelayed(this, 50) // 调用自身 重复绘制 46 | } 47 | } 48 | 49 | constructor(context: Context) : super(context) { 50 | init() 51 | } 52 | 53 | private fun init() { 54 | mMatrix = Matrix() 55 | // 画圆用到的paint 56 | mPaintCircle = Paint() 57 | mPaintCircle.style = Paint.Style.STROKE // 描边 58 | mPaintCircle.strokeWidth = 1f // 宽度 59 | mPaintCircle.alpha = 100 // 透明度 60 | mPaintCircle.isAntiAlias = true // 抗锯齿 61 | mPaintCircle.color = Color.parseColor("#B0C4DE") // 设置颜色 亮钢兰色 62 | 63 | // 扫描用到的paint 64 | mPaintRadar = Paint() 65 | mPaintRadar.style = Paint.Style.FILL_AND_STROKE // 填充 66 | mPaintRadar.isAntiAlias = true // 抗锯齿 67 | 68 | 69 | post(run) 70 | } 71 | 72 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { 73 | init() 74 | } 75 | 76 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} 77 | 78 | 79 | override fun onDraw(canvas: Canvas) { 80 | super.onDraw(canvas) 81 | 82 | Log.d(TAG,"onDraw()") 83 | for (i in pots.indices) { 84 | canvas.drawCircle((mWidth / 2).toFloat(), (mHeight / 2).toFloat(), mWidth * pots[i], mPaintCircle) 85 | } 86 | 87 | // 画布的旋转变换 需要调用save() 和 restore() 88 | canvas.save() 89 | 90 | scanShader = SweepGradient( 91 | (mWidth / 2).toFloat(), (mHeight / 2).toFloat(), 92 | intArrayOf(Color.TRANSPARENT, Color.parseColor("#84B5CA")), null 93 | ) 94 | mPaintRadar.shader = scanShader // 设置着色器 95 | canvas.concat(mMatrix) 96 | canvas.drawCircle((mWidth / 2).toFloat(), (mHeight / 2).toFloat(), mWidth * pots[6], mPaintRadar) 97 | 98 | canvas.restore() 99 | } 100 | 101 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 102 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 103 | Log.d(TAG,"onMeasure()") 104 | // 取屏幕的宽高是为了把雷达放在屏幕的中间 105 | mWidth = measuredWidth 106 | mHeight = measuredHeight 107 | mHeight = Math.min(mWidth, mHeight) 108 | mWidth = mHeight 109 | } 110 | 111 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/paint/ShadowLayerView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.paint 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.view.View 9 | import android.icu.lang.UCharacter.GraphemeClusterBreak.T 10 | 11 | 12 | /** 13 | *
14 | * author : devyk on 2019-11-29 22:21 15 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 16 | * github : https://github.com/yangkun19921001 17 | * mailbox : yang1001yk@gmail.com 18 | * desc : This is ShadowLayerView 19 | *20 | */ 21 | class ShadowLayerView : View { 22 | val paint = Paint() 23 | constructor(context: Context?) : super(context) 24 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 25 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) 26 | 27 | 28 | override fun draw(canvas: Canvas) { 29 | super.draw(canvas) 30 | 31 | 32 | paint.setStyle(Paint.Style.FILL) 33 | paint.setColor(Color.BLACK) 34 | // 设置透明度,要在setColor后面设置才生效 35 | paint.setAlpha(80) 36 | // 如果不关闭硬件加速,setShadowLayer无效 37 | setLayerType(View.LAYER_TYPE_SOFTWARE, null) 38 | // (阴影的半径,X轴方向上相对主体的位移,Y轴相对位移) 39 | paint.setShadowLayer(50f, 10f, 10f, Color.RED) 40 | paint.setTextSize(50f) 41 | 42 | // cx和cy为圆点的坐标 43 | val radius = 200 44 | val offest = 40 45 | 46 | val startX = width / 2 - radius 47 | val startY = height / 2 48 | canvas.drawText("画一个圆", width / 2 - 100f, height / 2f - 300, paint) 49 | canvas.drawCircle(startX.toFloat(), startY.toFloat(), radius.toFloat(), paint) 50 | 51 | paint.setStyle(Paint.Style.STROKE) 52 | paint.setStrokeWidth(5f) 53 | paint.setShadowLayer(50f, -20f, 10f, Color.RED) 54 | canvas.drawCircle(startX + radius * 2 + offest.toFloat(), startY.toFloat(), radius.toFloat(), paint) 55 | 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/paint/ZoomImageView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.paint 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.graphics.drawable.ShapeDrawable 6 | import android.graphics.drawable.shapes.OvalShape 7 | import android.util.AttributeSet 8 | import android.util.Log 9 | import android.view.MotionEvent 10 | import android.view.View 11 | import com.devyk.custom_view.R 12 | 13 | /** 14 | *
15 | * author : devyk on 2019-11-30 19:56 16 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 17 | * github : https://github.com/yangkun19921001 18 | * mailbox : yang1001yk@gmail.com 19 | * desc : This is ZoomImageView 20 | *21 | */ 22 | /* 23 | * 放大镜效果 24 | */ 25 | 26 | class ZoomImageView : View { 27 | // 原图 28 | private val mBitmap: Bitmap 29 | // 放大后的图 30 | private var mBitmapScale: Bitmap? = null 31 | // 制作的圆形的图片(放大的局部),盖在Canvas上面 32 | private val mShapeDrawable: ShapeDrawable 33 | 34 | private val mMatrix: Matrix 35 | 36 | constructor(context: Context?) : super(context) 37 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 38 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) 39 | 40 | init { 41 | 42 | mBitmap = BitmapFactory.decodeResource(resources, R.mipmap.gild_3) 43 | mBitmapScale = mBitmap 44 | //放大后的整个图片 45 | mBitmapScale = Bitmap.createScaledBitmap( 46 | mBitmapScale!!, mBitmapScale!!.width * FACTOR, 47 | mBitmapScale!!.height * FACTOR, true 48 | ) 49 | val bitmapShader = BitmapShader( 50 | mBitmapScale!!, Shader.TileMode.CLAMP, 51 | Shader.TileMode.CLAMP 52 | ) 53 | 54 | mShapeDrawable = ShapeDrawable(OvalShape()) 55 | mShapeDrawable.paint.shader = bitmapShader 56 | // 切出矩形区域,用来画圆(内切圆) 57 | mShapeDrawable.setBounds(0, 0, RADIUS * 2, RADIUS * 2) 58 | 59 | mMatrix = Matrix() 60 | } 61 | 62 | 63 | 64 | 65 | override fun onDraw(canvas: Canvas) { 66 | super.onDraw(canvas) 67 | 68 | 69 | // 1、画原图 70 | canvas.drawBitmap(mBitmap, 0f, 0f, null) 71 | 72 | // 2、画放大镜的图 73 | mShapeDrawable.draw(canvas) 74 | } 75 | 76 | 77 | override fun onTouchEvent(event: MotionEvent): Boolean { 78 | val x = event.x.toInt() 79 | val y = event.y.toInt() - RADIUS 80 | 81 | Log.d("onTouchEvent", "x:" + x + "y:" + y) 82 | 83 | // 将放大的图片往相反的方向挪动 84 | mMatrix.setTranslate((RADIUS - x * FACTOR).toFloat(), (RADIUS - y * FACTOR).toFloat()) 85 | mShapeDrawable.paint.shader.setLocalMatrix(mMatrix) 86 | // 切出手势区域点位置的圆 87 | mShapeDrawable.setBounds(x - RADIUS, y - RADIUS, x + RADIUS, y + RADIUS) 88 | // invalidate() 89 | postInvalidate() 90 | return true 91 | } 92 | 93 | companion object { 94 | 95 | //放大倍数 96 | private val FACTOR = 3 97 | //放大镜的半径 98 | private val RADIUS = 300 99 | } 100 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/paint/xfermode/GuaGuaCard.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.paint.xfermode 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.devyk.custom_view.R 9 | 10 | /** 11 | *
12 | * author : devyk on 2019-12-01 15:05 13 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 14 | * github : https://github.com/yangkun19921001 15 | * mailbox : yang1001yk@gmail.com 16 | * desc : This is GuaGuaCard 17 | *18 | */ 19 | class GuaGuaCardView(context: Context, attrs: AttributeSet) : View(context, attrs) { 20 | private val mBitPaint: Paint 21 | private val BmpDST: Bitmap 22 | private val BmpSRC: Bitmap 23 | private val BmpText: Bitmap 24 | private val mPath: Path 25 | private var mPreX: Float = 0.toFloat() 26 | private var mPreY: Float = 0.toFloat() 27 | 28 | init { 29 | 30 | setLayerType(View.LAYER_TYPE_SOFTWARE, null) 31 | mBitPaint = Paint() 32 | mBitPaint.color = Color.RED 33 | mBitPaint.style = Paint.Style.STROKE 34 | mBitPaint.strokeWidth = 45f 35 | 36 | BmpText = BitmapFactory.decodeResource(resources, R.drawable.guaguaka_text1, null) 37 | BmpSRC = BitmapFactory.decodeResource(resources, R.drawable.guaguaka, null) 38 | BmpDST = Bitmap.createBitmap(BmpSRC.width, BmpSRC.height, Bitmap.Config.ARGB_8888) 39 | mPath = Path() 40 | } 41 | 42 | override fun onDraw(canvas: Canvas) { 43 | super.onDraw(canvas) 44 | 45 | canvas.drawBitmap(BmpText, 0f, 0f, mBitPaint) 46 | 47 | val layerId = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG) 48 | 49 | //先把手指轨迹画到目标Bitmap上 50 | val c = Canvas(BmpDST) 51 | c.drawPath(mPath, mBitPaint) 52 | 53 | //然后把目标图像画到画布上 54 | canvas.drawBitmap(BmpDST, 0f, 0f, mBitPaint) 55 | 56 | //计算源图像区域 57 | mBitPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT) 58 | canvas.drawBitmap(BmpSRC, 0f, 0f, mBitPaint) 59 | 60 | mBitPaint.xfermode = null 61 | canvas.restoreToCount(layerId) 62 | } 63 | 64 | override fun onTouchEvent(event: MotionEvent): Boolean { 65 | when (event.action) { 66 | MotionEvent.ACTION_DOWN -> { 67 | mPath.moveTo(event.x, event.y) 68 | mPreX = event.x 69 | mPreY = event.y 70 | return true 71 | } 72 | MotionEvent.ACTION_MOVE -> { 73 | val endX = (mPreX + event.x) / 2 74 | val endY = (mPreY + event.y) / 2 75 | mPath.quadTo(mPreX, mPreY, endX, endY) 76 | mPreX = event.x 77 | mPreY = event.y 78 | } 79 | MotionEvent.ACTION_UP -> { 80 | } 81 | } 82 | postInvalidate() 83 | return super.onTouchEvent(event) 84 | } 85 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/paint/xfermode/HeartView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.paint.xfermode 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 android.widget.Toast 11 | import com.devyk.custom_view.R 12 | 13 | /** 14 | *
15 | * author : devyk on 2019-12-01 14:37 16 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 17 | * github : https://github.com/yangkun19921001 18 | * mailbox : yang1001yk@gmail.com 19 | * desc : This is HeartView 20 | *21 | */ 22 | class HeartView(context: Context, attrs: AttributeSet) : View(context, attrs) { 23 | 24 | private val mPaint: Paint 25 | private var mItemWaveLength = 0 26 | private var dx = 0 27 | 28 | private val BmpSRC: Bitmap 29 | private val BmpDST: Bitmap 30 | 31 | init { 32 | 33 | mPaint = Paint() 34 | mPaint.color = Color.RED 35 | 36 | BmpDST = BitmapFactory.decodeResource(resources, R.drawable.heartmap, null) 37 | BmpSRC = Bitmap.createBitmap(BmpDST.width, BmpDST.height, Bitmap.Config.ARGB_8888) 38 | 39 | mItemWaveLength = BmpDST.width 40 | 41 | startAnim() 42 | } 43 | 44 | override fun onDraw(canvas: Canvas) { 45 | super.onDraw(canvas) 46 | 47 | val c = Canvas(BmpSRC) 48 | //清空bitmap 49 | c.drawColor(Color.RED, PorterDuff.Mode.CLEAR) 50 | Log.d("onDraw","左移动:${BmpDST.width - dx}"); 51 | 52 | //画上矩形 53 | c.drawRect((BmpDST.width - dx).toFloat(), 0f, BmpDST.width.toFloat(), BmpDST.height.toFloat(), mPaint) 54 | 55 | //模式合成 56 | val layerId = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG) 57 | canvas.drawBitmap(BmpDST, 0f, 0f, mPaint) 58 | mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN) 59 | canvas.drawBitmap(BmpSRC, 0f, 0f, mPaint) 60 | mPaint.xfermode = null 61 | canvas.restoreToCount(layerId) 62 | } 63 | 64 | 65 | fun startAnim() { 66 | val animator = ValueAnimator.ofInt(0, mItemWaveLength) 67 | animator.duration = 6000 68 | animator.repeatCount = ValueAnimator.INFINITE 69 | animator.interpolator = LinearInterpolator() 70 | animator.addUpdateListener { animation -> 71 | dx = animation.animatedValue as Int 72 | postInvalidate() 73 | } 74 | animator.start() 75 | } 76 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/paint/xfermode/InvertedImageView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.paint.xfermode 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.util.AttributeSet 6 | import android.view.View 7 | import com.devyk.custom_view.BitmapUtis 8 | import com.devyk.custom_view.R 9 | 10 | /** 11 | *
12 | * author : devyk on 2019-12-01 14:03 13 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 14 | * github : https://github.com/yangkun19921001 15 | * mailbox : yang1001yk@gmail.com 16 | * desc : This is InvertedImageView 17 | *18 | */ 19 | 20 | class InvertedImageView(context: Context, attrs: AttributeSet) : View(context, attrs) { 21 | private val mBitPaint: Paint 22 | private val BmpDST: Bitmap 23 | private val BmpSRC: Bitmap 24 | private val BmpRevert: Bitmap 25 | 26 | init { 27 | setLayerType(View.LAYER_TYPE_SOFTWARE, null) 28 | mBitPaint = Paint() 29 | BmpDST = BitmapUtis.changeBitmapSize(context,R.mipmap.gild_3) 30 | BmpSRC = BitmapUtis.changeBitmapSize(context,R.drawable.invert_shade) 31 | 32 | val matrix = Matrix() 33 | matrix.setScale(1f, -1f) 34 | // 生成倒影图 35 | BmpRevert = Bitmap.createBitmap(BmpDST, 0, 0, BmpDST.width, BmpDST.height, matrix, true) 36 | } 37 | 38 | override fun onDraw(canvas: Canvas) { 39 | super.onDraw(canvas) 40 | 41 | canvas.drawColor(Color.BLACK) 42 | 43 | 44 | //先画出原始图片 45 | canvas.drawBitmap(BmpDST, 0f, 0f, mBitPaint) 46 | 47 | //再画出倒影 48 | val layerId = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG) 49 | canvas.translate(0f, BmpSRC.height.toFloat()) 50 | 51 | canvas.drawBitmap(BmpRevert, 0f, 0f, mBitPaint) 52 | mBitPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN) 53 | canvas.drawBitmap(BmpSRC, 0f, 0f, mBitPaint) 54 | 55 | mBitPaint.xfermode = null 56 | 57 | canvas.restoreToCount(layerId) 58 | } 59 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/paint/xfermode/IrregularWaveView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.paint.xfermode 2 | 3 | import android.animation.ValueAnimator 4 | import android.content.Context 5 | import android.graphics.* 6 | import android.util.AttributeSet 7 | import android.view.View 8 | import android.view.animation.LinearInterpolator 9 | import com.devyk.custom_view.R 10 | 11 | /** 12 | *
13 | * author : devyk on 2019-12-01 14:20 14 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 15 | * github : https://github.com/yangkun19921001 16 | * mailbox : yang1001yk@gmail.com 17 | * desc : This is IrregularWaveView 18 | *19 | */ 20 | class IrregularWaveView(context: Context, attrs: AttributeSet) : View(context, attrs) { 21 | 22 | private val mPaint: Paint 23 | private var mItemWaveLength = 0 24 | private var dx = 0 25 | 26 | private val BmpSRC: Bitmap 27 | private val BmpDST: Bitmap 28 | 29 | init { 30 | mPaint = Paint() 31 | 32 | BmpDST = BitmapFactory.decodeResource(resources, R.drawable.wav, null) 33 | BmpSRC = BitmapFactory.decodeResource(resources, R.drawable.circle_shape, null) 34 | //不要让它超出边界 35 | mItemWaveLength = BmpDST.width - BmpSRC.width 36 | 37 | startAnim() 38 | } 39 | 40 | 41 | override fun onDraw(canvas: Canvas) { 42 | super.onDraw(canvas) 43 | 44 | //先画上圆形 45 | canvas.drawBitmap(BmpSRC, 0f, 0f, mPaint) 46 | 47 | //再画上结果 48 | val layerId = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG) 49 | canvas.drawBitmap( 50 | BmpDST, 51 | Rect(dx, 0, dx + BmpSRC.width, BmpSRC.height), 52 | Rect(0, 0, BmpSRC.width, BmpSRC.height), 53 | mPaint 54 | ) 55 | mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN) 56 | canvas.drawBitmap(BmpSRC, 0f, 0f, mPaint) 57 | mPaint.xfermode = null 58 | canvas.restoreToCount(layerId) 59 | 60 | 61 | } 62 | 63 | 64 | fun startAnim() { 65 | val animator = ValueAnimator.ofInt(0, mItemWaveLength) 66 | animator.duration = 2000 67 | animator.repeatCount = ValueAnimator.INFINITE 68 | animator.interpolator = LinearInterpolator() 69 | animator.addUpdateListener { animation -> 70 | dx = animation.animatedValue as Int 71 | postInvalidate() 72 | } 73 | animator.start() 74 | } 75 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/paint/xfermode/RoudImageView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.paint.xfermode 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.util.AttributeSet 6 | import android.util.Log 7 | import android.view.View 8 | import com.devyk.custom_view.BitmapUtis 9 | import com.devyk.custom_view.R 10 | 11 | /** 12 | *
13 | * author : devyk on 2019-12-01 13:15 14 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 15 | * github : https://github.com/yangkun19921001 16 | * mailbox : yang1001yk@gmail.com 17 | * desc : This is RoudImageView 18 | *19 | */ 20 | 21 | public class RoudImageView : View { 22 | 23 | constructor(context: Context?) : super(context) 24 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { 25 | init() 26 | } 27 | var mBitPaint = Paint() 28 | private lateinit var mBmpDST: Bitmap 29 | private lateinit var mBmpSRC: Bitmap 30 | private fun init() { 31 | setLayerType(LAYER_TYPE_SOFTWARE, null) 32 | mBmpDST = BitmapUtis.changeBitmapSize(context,R.mipmap.gild_3) 33 | mBmpSRC = BitmapUtis.changeBitmapSize(context,R.drawable.shade) 34 | } 35 | 36 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) 37 | 38 | 39 | override fun draw(canvas: Canvas) { 40 | super.draw(canvas) 41 | val saveLayer = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG) 42 | canvas.drawBitmap(mBmpDST,0f,0f,mBitPaint) 43 | mBitPaint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.DST_IN)) 44 | canvas.drawBitmap(mBmpSRC,0f,0f,mBitPaint) 45 | 46 | mBitPaint.setXfermode(null) 47 | canvas.restoreToCount(saveLayer) 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/paint/xfermode/TwitterView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.paint.xfermode 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.util.AttributeSet 6 | import android.view.View 7 | import com.devyk.custom_view.R 8 | 9 | /** 10 | *
11 | * author : devyk on 2019-12-01 14:58 12 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 13 | * github : https://github.com/yangkun19921001 14 | * mailbox : yang1001yk@gmail.com 15 | * desc : This is TwitterView 16 | *17 | */ 18 | class TwitterView(context: Context, attrs: AttributeSet) : View(context, attrs) { 19 | private val mBitPaint: Paint 20 | private val BmpDST: Bitmap 21 | private val BmpSRC: Bitmap 22 | 23 | init { 24 | setLayerType(View.LAYER_TYPE_SOFTWARE, null) 25 | mBitPaint = Paint() 26 | //目标图像 27 | BmpDST = BitmapFactory.decodeResource(resources, R.drawable.twiter_bg, null) 28 | //原图像 29 | BmpSRC = BitmapFactory.decodeResource(resources, R.drawable.twiter_light, null) 30 | } 31 | 32 | override fun onDraw(canvas: Canvas) { 33 | super.onDraw(canvas) 34 | 35 | val layerId = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG) 36 | 37 | canvas.drawBitmap(BmpDST, 0f, 0f, mBitPaint) 38 | mBitPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY) 39 | canvas.drawBitmap(BmpSRC, 0f, 0f, mBitPaint) 40 | 41 | mBitPaint.xfermode = null 42 | canvas.restoreToCount(layerId) 43 | } 44 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/svg/map/Dom2PathData.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.svg.map 2 | 3 | /** 4 | *
5 | * author : devyk on 2019-12-06 14:47 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is Dom2PathData 10 | *11 | */ 12 | class PathDataHandle { 13 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/svg/map/Dom2Xml.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.svg.map 2 | 3 | import android.graphics.RectF 4 | import android.icu.util.RangeValueIterator 5 | import com.devyk.custom_view.svg.PathParser 6 | import org.w3c.dom.Element 7 | import java.io.InputStream 8 | import java.util.ArrayList 9 | import javax.xml.parsers.DocumentBuilderFactory 10 | 11 | /** 12 | *
13 | * author : devyk on 2019-12-06 14:49 14 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 15 | * github : https://github.com/yangkun19921001 16 | * mailbox : yang1001yk@gmail.com 17 | * desc : This is Dom2Xml 18 | *19 | */ 20 | class Dom2XmlUtils { 21 | companion object { 22 | /** 23 | * 定义的是 map 的矩形 24 | */ 25 | public var MAP_RECTF = RectF() 26 | /** 27 | * xml path 节点 28 | */ 29 | private val PATH_TAG = "path" 30 | /** 31 | * 定义一个所有身份的集合 32 | */ 33 | public val mapDataLists = ArrayList
7 | * author : devyk on 2019-12-06 14:26 8 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 9 | * github : https://github.com/yangkun19921001 10 | * mailbox : yang1001yk@gmail.com 11 | * desc : This is MapData 管理 Map Path 数据 12 | *13 | */ 14 | data class MapData( 15 | val name: String = "", 16 | val fillColor: String = "", 17 | val strokeColor: String = "", 18 | val strokeWidth: String = "", 19 | val pathData: Path, 20 | var isSelect: Boolean = false 21 | ) { 22 | 23 | } -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/utils.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view 2 | 3 | /** 4 | *
5 | * author : devyk on 2019-11-29 22:11 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is utils 10 | *11 | */ -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/utils/HelpDraw.java: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.utils; 2 | 3 | import android.graphics.*; 4 | 5 | /** 6 | *
7 | * author : devyk on 2019-12-01 19:59 8 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 9 | * github : https://github.com/yangkun19921001 10 | * mailbox : yang1001yk@gmail.com 11 | * desc : This is HelpDraw 12 | *13 | */ 14 | public class HelpDraw { 15 | /** 16 | * 绘制网格 17 | * @param canvas 画布 18 | * @param winSize 屏幕尺寸 19 | * @param paint 画笔 20 | */ 21 | public static void drawGrid(Canvas canvas, Point winSize, Paint paint) { 22 | //初始化网格画笔 23 | paint.setStrokeWidth(2); 24 | paint.setColor(Color.GRAY); 25 | paint.setStyle(Paint.Style.STROKE); 26 | //设置虚线效果new float[]{可见长度, 不可见长度},偏移值 27 | paint.setPathEffect(new DashPathEffect(new float[]{10, 5}, 0)); 28 | canvas.drawPath(HelpPath.gridPath(50, winSize), paint); 29 | } 30 | 31 | 32 | /** 33 | * 绘制坐标系 34 | * @param canvas 画布 35 | * @param coo 坐标系原点 36 | * @param winSize 屏幕尺寸 37 | * @param paint 画笔 38 | */ 39 | public static void drawCoo(Canvas canvas, Point coo, Point winSize, Paint paint) { 40 | //初始化网格画笔 41 | paint.setStrokeWidth(10); 42 | paint.setColor(Color.RED); 43 | paint.setStyle(Paint.Style.STROKE); 44 | //设置虚线效果new float[]{可见长度, 不可见长度},偏移值 45 | paint.setPathEffect(null); 46 | //绘制直线 47 | canvas.drawPath(HelpPath.cooPath(coo, winSize), paint); 48 | //左箭头 49 | canvas.drawLine(winSize.x, coo.y, winSize.x - 40, coo.y - 20, paint); 50 | canvas.drawLine(winSize.x, coo.y, winSize.x - 40, coo.y + 20, paint); 51 | //下箭头 52 | canvas.drawLine(coo.x, winSize.y, coo.x - 20, winSize.y - 40, paint); 53 | canvas.drawLine(coo.x, winSize.y, coo.x + 20, winSize.y - 40, paint); 54 | //为坐标系绘制文字 55 | drawText4Coo(canvas, coo, winSize, paint); 56 | } 57 | /** 58 | * 为坐标系绘制文字 59 | * 60 | * @param canvas 画布 61 | * @param coo 坐标系原点 62 | * @param winSize 屏幕尺寸 63 | * @param paint 画笔 64 | */ 65 | private static void drawText4Coo(Canvas canvas, Point coo, Point winSize, Paint paint) { 66 | //绘制文字 67 | paint.setTextSize(10); 68 | canvas.drawText("x", winSize.x - 60, coo.y - 40, paint); 69 | canvas.drawText("y", coo.x - 40, winSize.y - 60, paint); 70 | paint.setTextSize(25); 71 | //X正轴文字 72 | for (int i = 1; i < (winSize.x - coo.x) / 50; i++) { 73 | paint.setStrokeWidth(2); 74 | canvas.drawText(100 * i + "", coo.x - 20 + 100 * i, coo.y + 40, paint); 75 | paint.setStrokeWidth(5); 76 | canvas.drawLine(coo.x + 100 * i, coo.y, coo.x + 100 * i, coo.y - 10, paint); 77 | } 78 | //X负轴文字 79 | for (int i = 1; i < coo.x / 50; i++) { 80 | paint.setStrokeWidth(2); 81 | canvas.drawText(-100 * i + "", coo.x - 20 - 100 * i, coo.y + 40, paint); 82 | paint.setStrokeWidth(5); 83 | canvas.drawLine(coo.x - 100 * i, coo.y, coo.x - 100 * i, coo.y - 10, paint); 84 | } 85 | //y正轴文字 86 | for (int i = 1; i < (winSize.y - coo.y) / 50; i++) { 87 | paint.setStrokeWidth(2); 88 | canvas.drawText(100 * i + "", coo.x + 20, coo.y + 10 + 100 * i, paint); 89 | paint.setStrokeWidth(5); 90 | canvas.drawLine(coo.x, coo.y + 100 * i, coo.x + 10, coo.y + 100 * i, paint); 91 | } 92 | //y负轴文字 93 | for (int i = 1; i < coo.y / 50; i++) { 94 | paint.setStrokeWidth(2); 95 | canvas.drawText(-100 * i + "", coo.x + 20, coo.y + 10 - 100 * i, paint); 96 | paint.setStrokeWidth(5); 97 | canvas.drawLine(coo.x, coo.y - 100 * i, coo.x + 10, coo.y - 100 * i, paint); 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/utils/HelpPath.java: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.utils; 2 | 3 | import android.graphics.Path; 4 | import android.graphics.Point; 5 | 6 | /** 7 | *
8 | * author : devyk on 2019-12-01 19:58 9 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 10 | * github : https://github.com/yangkun19921001 11 | * mailbox : yang1001yk@gmail.com 12 | * desc : This is HelpPath 13 | *14 | */ 15 | public class HelpPath { 16 | /** 17 | * 绘制网格:注意只有用path才能绘制虚线 18 | * 19 | * @param step 小正方形边长 20 | * @param winSize 屏幕尺寸 21 | */ 22 | public static Path gridPath(int step, Point winSize) { 23 | Path path = new Path(); 24 | for (int i = 0; i < winSize.y / step + 1; i++) { 25 | path.moveTo(0, step * i); 26 | path.lineTo(winSize.x, step * i); 27 | } 28 | for (int i = 0; i < winSize.x / step + 1; i++) { 29 | path.moveTo(step * i, 0); 30 | path.lineTo(step * i, winSize.y); 31 | } 32 | return path; 33 | } 34 | 35 | /** 36 | * 坐标系路径 37 | * 38 | * @param coo 坐标点 39 | * @param winSize 屏幕尺寸 40 | * @return 坐标系路径 41 | */ 42 | public static Path cooPath(Point coo, Point winSize) { 43 | Path path = new Path(); 44 | //x正半轴线 45 | path.moveTo(coo.x, coo.y); 46 | path.lineTo(winSize.x, coo.y); 47 | //x负半轴线 48 | path.moveTo(coo.x, coo.y); 49 | path.lineTo(coo.x - winSize.x, coo.y); 50 | //y负半轴线 51 | path.moveTo(coo.x, coo.y); 52 | path.lineTo(coo.x, coo.y - winSize.y); 53 | //y负半轴线 54 | path.moveTo(coo.x, coo.y); 55 | path.lineTo(coo.x, winSize.y); 56 | return path; 57 | } 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /custom_view/src/main/java/com/devyk/custom_view/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package com.devyk.custom_view.utils; 2 | 3 | import android.content.Context; 4 | import android.graphics.Point; 5 | import android.util.DisplayMetrics; 6 | import android.view.Window; 7 | import android.view.WindowManager; 8 | 9 | /** 10 | *
11 | * author : devyk on 2019-12-01 19:59 12 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 13 | * github : https://github.com/yangkun19921001 14 | * mailbox : yang1001yk@gmail.com 15 | * desc : This is Utils 16 | *17 | */ 18 | public class Utils { 19 | /** 20 | * 获得屏幕高度 21 | * 22 | * @param ctx 上下文 23 | * @param winSize 屏幕尺寸 24 | */ 25 | public static void loadWinSize(Context ctx, Point winSize) { 26 | WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE); 27 | DisplayMetrics outMetrics = new DisplayMetrics(); 28 | if (wm != null) { 29 | wm.getDefaultDisplay().getMetrics(outMetrics); 30 | } 31 | winSize.x = outMetrics.widthPixels; 32 | winSize.y = outMetrics.heightPixels; 33 | } 34 | 35 | /** 36 | * 设置全屏 37 | * @param window 38 | */ 39 | public static void setActivityFullScreen(Window window) { 40 | window.setFlags( 41 | WindowManager.LayoutParams.FLAG_FULLSCREEN, 42 | WindowManager.LayoutParams.FLAG_FULLSCREEN 43 | ); 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /custom_view/src/main/res/drawable/arrow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/CustomViewSample/791b3e93dd641c95d2d7381a33278ef320663c41/custom_view/src/main/res/drawable/arrow.jpg -------------------------------------------------------------------------------- /custom_view/src/main/res/drawable/car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/CustomViewSample/791b3e93dd641c95d2d7381a33278ef320663c41/custom_view/src/main/res/drawable/car.png -------------------------------------------------------------------------------- /custom_view/src/main/res/drawable/circle_shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/CustomViewSample/791b3e93dd641c95d2d7381a33278ef320663c41/custom_view/src/main/res/drawable/circle_shape.png -------------------------------------------------------------------------------- /custom_view/src/main/res/drawable/flag_01.xml: -------------------------------------------------------------------------------- 1 | 2 |