1. 程式人生 > >Android 佈局手勢縮放,可滑動檢視,實現類似Imgview圖片縮放效果

Android 佈局手勢縮放,可滑動檢視,實現類似Imgview圖片縮放效果

Android 自定義View 佈局手勢縮放,可滑動檢視,子View實現類似Imgview圖片縮放效果

自定義View,使用ViewDragHelper和ScaleGestureDetector結合簡單的實現類似PhotoView的效果的控制元件,使佈局內的子view能隨著手勢進行縮放,滑動檢視的效果


package com.example.lbq.one.view;

import android.content.Context;
import android.graphics.Canvas;
import android.support.v4.widget.ViewDragHelper;
import
android.util.AttributeSet; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import android.view.ViewGroup; import android.widget.RelativeLayout; /** * Created by lbq on 2018/1/9. */ public class GestureView extends RelativeLayout { private ViewDragHelper mViewDragHelper;//用於處理子View 的滑動
private ScaleGestureDetector mGesture;//用與處理雙手的縮放手勢 private float mZoomScale = 1.0f;//預設的縮放比為1 private Context mContext; public GestureView(Context context) { this(context, null); } public GestureView(Context context, AttributeSet attrs) { this(context, attrs, 0
); } public GestureView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; init(); } private void init() { initViewDrag(); mGesture = new ScaleGestureDetector(mContext, new ScaleGestureDetector.OnScaleGestureListener() { //隨著手勢操作,回撥的方法, @Override public boolean onScale(ScaleGestureDetector detector) { float previousSpan = detector.getPreviousSpan();//縮放發生前的兩點距離 float currentSpan = detector.getCurrentSpan();//縮放發生時的兩點距離 if (previousSpan < currentSpan)//放大 { mZoomScale = mZoomScale + (currentSpan - previousSpan) / previousSpan; } else { mZoomScale = mZoomScale - (previousSpan - currentSpan) / previousSpan; } //確保放大最多為2倍,最少不能小於原圖 if (mZoomScale > 2) { mZoomScale = 2; } else if (mZoomScale < 1) { mZoomScale = 1; } setScaleX(mZoomScale); setScaleY(mZoomScale); //這裡呼叫的是本自定義View的方法,是對本自定義view進行的縮放 /*在這裡呼叫getChildView(index)的進行縮放,雖然控制元件顯示大小改變了,但是在ViewDragHelper的回撥方法中獲得的View child的getWidth()和getHeigit()是原來的大小,不會發生改變*/ return true; } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { return true; } /** * @param detector */ @Override public void onScaleEnd(ScaleGestureDetector detector) { } }); } //將需要的view放置在佈局中 public void addChildView(final View view) { //保證控制元件只有一個子View,方便操作 if (getChildCount() != 0) { throw new IllegalStateException("this view can only have one child!"); } else { addView(view); //確保子view永遠鋪滿控制元件 RelativeLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); view.setLayoutParams(params); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); } @Override public boolean onTouchEvent(MotionEvent event) { //將事件交給ViewDragHelper和ScaleGestureDetector 處理 mViewDragHelper.processTouchEvent(event); return mGesture.onTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { //由ViewDragHelper處理攔截 return mViewDragHelper.shouldInterceptTouchEvent(ev); } private void initViewDrag() { mViewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() { @Override public boolean tryCaptureView(View child, int pointerId) { //這裡的return true表示自view可以滑動,false表示不處理滑動 return true; } /*這裡是控制子view左右滑動的回撥,child為本自定義view的子控制元件,left表示意圖從手指操作子view從左邊界滑動的距離,大於0表示向右移動,小於0表示向左移動,是從手指的移動測出的理論值。方法的返回值表示實際上控制元件移動的距離,可以用返回值控制邊界*/ @Override public int clampViewPositionHorizontal(View child, int left, int dx) { //放大後控制元件的寬度-主佈局的寬度 因為View chlid的控制元件使用的是match_parent,所以直接用父佈局的寬取值 if (left < (getWidth() - getWidth() * mZoomScale) / 2) { return (int) ((getWidth() - getWidth() * mZoomScale) / 2); } if (left > ((mZoomScale * getWidth() - getWidth()) / 2)) { return (int) ((mZoomScale * getWidth() - getWidth()) / 2); } return left; /** 這裡進行邊界處理,因為這裡實際最大能放大2倍,那麼實際會有3/4的左右滑動空間才會把子View(child)完全劃出可見範圍,這裡為了保證view的可見性,使用1/2確保子view始終在可見範圍之內*/ } /*這個方法是控制上下滑動的,跟上面的方法意義一樣,只是方向上不一樣,top表示從頂部邊界計算的距離,向下為正,向上為負*/ @Override public int clampViewPositionVertical(View child, int top, int dy) { if (top < (getHeight() - getHeight() * mZoomScale) / 2) { return ((int) (getHeight() - getHeight() * mZoomScale) / 2); } if (top > (mZoomScale * getHeight() - getHeight()) / 2) { return (int) ((mZoomScale * getHeight() - getHeight()) / 2); } return top; } }); } }