XML
文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<com.gallery20.app.MyImageView
android:id="@+id/real_iv"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_marginTop="30dp"
android:layout_marginBottom="30dp"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:background="#00000000"/>
</LinearLayout>
Activity
代码
const val TAG = "Yang"
class MainActivity : AppCompatActivity() {
var mRealView: MyImageView? = null
var tempBitmap: Bitmap? = null
var screenWidth = 0
var screenHeight = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mRealView = findViewById(R.id.real_iv)
// 屏幕宽高的一半作为临时RectF, 用于压缩Bitmap
screenWidth = resources.displayMetrics.widthPixels
screenHeight = resources.displayMetrics.heightPixels
val tempRect = RectF(0f, 0f, screenWidth.toFloat() / 2, screenHeight.toFloat() / 2)
CoroutineScope(Dispatchers.IO).launch {
tempBitmap = getBitmap(resources, tempRect, R.drawable.fake)
withContext(Dispatchers.Main) {
mRealView?.setImageBitmap(tempBitmap)
}
}
}
}
fun getBitmap(resources : Resources, destRect : RectF, imageId: Int): Bitmap? {
var imageWidth = -1
var imageHeight = -1
val preOption = BitmapFactory.Options().apply {
// 只获取图片的宽高
inJustDecodeBounds = true
BitmapFactory.decodeResource(resources, imageId, this)
}
imageWidth = preOption.outWidth
imageHeight = preOption.outHeight
// 计算缩放比例
val scaleMatrix = Matrix()
// 确定未缩放Bitmap的RectF
var srcRect = RectF(0f, 0f, imageWidth.toFloat(), imageHeight.toFloat())
// 通过目标RectF, 确定缩放数值,存储在scaleMatrix中
scaleMatrix.setRectToRect(srcRect, destRect, Matrix.ScaleToFit.CENTER)
// 缩放数值再映射到原始Bitmap上,得到缩放后的RectF
scaleMatrix.mapRect(srcRect)
val finalOption = BitmapFactory.Options().apply {
if (imageHeight > 0 && imageWidth > 0) {
inPreferredConfig = Bitmap.Config.ARGB_8888
inSampleSize = calculateInSampleSize(
imageWidth,
imageHeight,
srcRect.width().toInt(),
srcRect.height().toInt()
)
}
}
return BitmapFactory.decodeResource(resources, imageId, finalOption)
}
fun calculateInSampleSize(fromWidth: Int, fromHeight: Int, toWidth: Int, toHeight: Int): Int {
var bitmapWidth = fromWidth
var bitmapHeight = fromHeight
if (fromWidth > toWidth|| fromHeight > toHeight) {
var inSampleSize = 1
// 计算最大的inSampleSize值,该值是2的幂,并保持原始宽高大于目标宽高
while (bitmapWidth >= toWidth && bitmapHeight >= toHeight) {
bitmapWidth /= 2
bitmapHeight /= 2
inSampleSize *= 2
}
return inSampleSize
}
return 1
}
自定义Activity
代码
class MyImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr){
private var lastX = 0f
private var lastY = 0f
private val scroller = Scroller(context)
// private val scroller = OverScroller(context)
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
lastX = event.rawX
lastY = event.rawY
}
MotionEvent.ACTION_MOVE -> {
val dx = event.rawX - lastX
val dy = event.rawY - lastY
val left = left + dx.toInt()
val top = top + dy.toInt()
val right = right + dx.toInt()
val bottom = bottom + dy.toInt()
layout(left, top, right, bottom)
lastX = event.rawX
lastY = event.rawY
}
MotionEvent.ACTION_UP -> {
// 手指抬起时,根据速度进行惯性滑动
// (int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)
// startX, startY:开始滑动的位置
// velocityX, velocityY:滑动的速度
// minX, maxX, minY, maxY:滑动的范围
val parentView = (parent as? View)
scroller.fling(left, top, 500, 500, 0, parentView?.width!!-width, 0, parentView?.height!!-height)
invalidate() // 请求重绘View,这会导致computeScroll()被调用
}
}
return true
}
override fun computeScroll() {
if (scroller.computeScrollOffset()) {
// 更新View的位置
val left = scroller.currX
val top = scroller.currY
val right = left + width
val bottom = top + height
layout(left, top, right, bottom)
if (!scroller.isFinished) {
invalidate() // 继续请求重绘View,直到滑动结束
Log.i("yang", "computeScroll: $left, $top, $right, $bottom")
}
}
}
}
Fling
惯性滑动效果是指在手指抬起后,即ACTION_UP
事件中计算惯性滑动后,当前View
移动的距离- 回弹效果的实现在调用
Scroller.fling()
方法或者OverScroller.fling()
方法时定义minX
,maxX
,minY
,maxY
定义四个参数的大小。一般minX
和minY
为0,如果能在父View
中进行拖拽滑动,不超出边界,则maxX
和maxY
为父View
的宽高减去需要拖拽滑动View
的宽高 Scroller
在进行越界滑动时没有提供弹性滑动,Scroller
会直接闪烁回指定滑动范围内OverScroller
在进行越界滑动时提供弹性滑动,OverScroller
会继续滑动一段距离,然后慢慢回弹,OverScroller.fling()
方法最后两个参数定义了超出滑动范围后水平和竖直方向可弹性减速滑动的最大距离,一般为当前滑动View
的宽高
效果图
Scroller
惯性滑动效果
OverScroller
惯性滑动效果