2015年2月16日

【Android】手勢觸碰 - 圖片(ImageView)滑動與縮放實作

各位Android安卓開發者大家好!

今天小黑人要與大家分享的文章內容是"圖片(ImageView)的滑動與放大縮小",就是運用手勢動作來針對圖片(ImageView)進行單點的滑動多點的縮放,因為現在許多圖片瀏覽的App裡針對圖片的檢視都是很直覺性的用手指來滑動圖片,相對的想要放大圖片仔細瀏覽或把圖片縮小看時都是運用兩隻手指頭來進行動作,而我們所使用的ImageView元件只是單純的做圖片檢視功能而無法針對圖片進行滑動與縮放等動作,所以小黑人今天要與大家分享的是簡單的覆寫ImageView元件直接達到手勢觸碰進行放大縮小等功能,那該怎麼做才能直接針對ImageView進行滑動縮放呢?就讓我們繼續看下去吧。


1. 首先在我們開始實作範例之前,先簡單的與大家介紹接下來要撰寫的三大步驟,第一是新增我們將要覆寫的ImageView class,我們會把滑動與縮放的功能機制加入到這個ImageView class裡,第二是Layout(.xml)畫面框架的排列,把我們覆寫的ImageView與正常的ImageView帶入來比較兩者差別,最後就是Activity(.java)的功能運用與狀態切換

2.
好,我們先新增一個ImageView class,小黑人命名為ScaleImage.java,這個ScaleImage是繼承ImageView來進行覆寫,ScaleImage完整程式碼如下 :

public class ScaleImage extends ImageView
{
        //
初始狀態的Matrix
        private Matrix mMatrix = new Matrix();
        //
進行變動狀況下的Matrix
        private Matrix mChangeMatrix = new Matrix();
        //
圖片的Bitmap
        private Bitmap mBitmap = null;
        //
手機畫面尺寸資訊
        private DisplayMetrics mDisplayMetrics;
        //
設定縮放最小比例
        private float mMinScale = 1.0f;
        //
設定縮放最大比例
        private float mMaxScale = 5.0f;
        //
圖片狀態 - 初始狀態
        private  static final int STATE_NONE = 0;
        //
圖片狀態 - 拖動狀態
        private static final int STATE_DRAG = 1;
        //
圖片狀態 - 縮放狀態
        private static final int STATE_ZOOM = 2;
        //
當下的狀態
        private int mState = STATE_NONE;
        //
第一點按下的座標
        private PointF mFirstPointF = new PointF();
        //
第二點按下的座標
        private PointF mSecondPointF = new PointF();
        //
兩點距離
        private float mDistance = 1f;
        //
圖片中心座標
        private float mCenterX,mCenterY;

        //ScaleImage
類別,xml呼叫運用
        public ScaleImage(Context context, AttributeSet attrs)
        {
                super(context, attrs);
               
                //
取得圖片Bitmap
                BitmapDrawable mBitmapDrawable = (BitmapDrawable) this.getDrawable();
                if(mBitmapDrawable != null)
                {
                        mBitmap = mBitmapDrawable.getBitmap();
                        build_image();
                }
        }
       
        //
圖片縮放層級設定
        private void Scale()
        {
                //
取得圖片縮放的層級
                float level[] = new float[9];
                mMatrix.getValues(level);
       
                //
狀態為縮放時進入
                if (mState == STATE_ZOOM)
                {
                         //
若層級小於1則縮放至原始大小
                         if (level[0] < mMinScale) 
                         {
                                 mMatrix.setScale(mMinScale, mMinScale);
                                 mMatrix.postTranslate(mCenterX,mCenterY);  
                         }
           
                         //
若縮放層級大於最大層級則顯示最大層級
                         if (level[0] > mMaxScale)  mMatrix.set(mChangeMatrix);
                }
         }
   
         //
兩點距離
         private float Spacing(MotionEvent event)
         {
                 float x = event.getX(0) - event.getX(1);
                 float y = event.getY(0) - event.getY(1);
                 return FloatMath.sqrt(x * x + y * y);
         }

         //
兩點中心
         private void MidPoint(PointF point, MotionEvent event)
         {
                 float x = event.getX(0) + event.getX(1);
                 float y = event.getY(0) + event.getY(1);
                 point.set(x / 2, y / 2);
         }
       
        //
圖片縮放設定
        public void build_image()
        {
                //
取得Context
                Context mContext = getContext();
                //
取得手機畫面尺寸資訊
                mDisplayMetrics = mContext.getResources().getDisplayMetrics();

                //
設置縮放的型態
                this.setScaleType(ScaleType.MATRIX);

                //
Bitmap帶入
                this.setImageBitmap(mBitmap);
               
                //
將圖片放置畫面中央
                mCenterX = (float)((mDisplayMetrics.widthPixels/2)-(mBitmap.getWidth()/2));
                mCenterY = (float)((mDisplayMetrics.heightPixels/3)-(mBitmap.getHeight()/2));
                mMatrix.postTranslate(mCenterX,mCenterY);  
               
                //
mMatrix帶入
                this.setImageMatrix(mMatrix);
               
                //
設置Touch觸發的Listener動作
                this.setOnTouchListener(new OnTouchListener()
                {
                        @Override
                        public boolean onTouch(View v, MotionEvent event)
                        {
                                //
多點觸碰偵測
                                 switch(event.getAction() & MotionEvent.ACTION_MASK)
                                 {
                                         //
第一點按下進入
                                         case MotionEvent.ACTION_DOWN :
                                                 mChangeMatrix.set(mMatrix);
                                                 mFirstPointF.set(event.getX(), event.getY());
                                                 mState = STATE_DRAG;
                                             break;
                                   
                                         //
第二點按下進入
                                         case MotionEvent.ACTION_POINTER_DOWN :
                                                 mDistance = Spacing(event);
                                                 //
只要兩點距離大於10就判定為多點觸碰
                                                 if (Spacing(event) > 10f)
                                                 {
                                                        mChangeMatrix.set(mMatrix);
                                                        MidPoint(mSecondPointF, event);
                                                        mState = STATE_ZOOM;
                                                 }
                                             break;
                                   
                                        //
離開觸碰
                                        case MotionEvent.ACTION_UP :
                                             break;
                               
                                        //
離開觸碰,狀態恢復
                                        case MotionEvent.ACTION_POINTER_UP :
                                                mState = STATE_NONE;
                                             break;
                                   
                                        //
滑動過程進入
                                        case MotionEvent.ACTION_MOVE :
                                                if (mState == STATE_DRAG)
                                                {
                                                        mMatrix.set(mChangeMatrix);
                                                        mMatrix.postTranslate(event.getX() - mFirstPointF.x, event.getY() - mFirstPointF.y);      
                                                }
                                                else if (mState == STATE_ZOOM)
                                                {
                                                        float NewDistance = Spacing(event);
                                                        if (NewDistance > 10f)
                                                        {
                                                                  mMatrix.set(mChangeMatrix);
                                                                  float NewScale = NewDistance / mDistance;
                                                                  mMatrix.postScale(NewScale, NewScale, mSecondPointF.x, mSecondPointF.y);
                                                        }
                                                }
                                             break;
                                 }
                               
                                 //
mMatrix滑動縮放控制帶入
                                 ScaleImage.this.setImageMatrix(mMatrix);
                                 //
縮放設定
                                 Scale();
                                      
                                 return true;
                        }
                });
        }
}

3.
我們已經新增好ScaleImage.java且撰寫好滑動與縮放的功能後,我們就可以針對Layout(.xml)來進行畫面框架的排列了,小黑人範例的main.xml如下 :

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
        <!--
標題文字-->
        <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textColor="@android:color/white"
        android:text="
小黑人的Android教室"
        android:textStyle="bold"
        />
        <!--
範例主題文字-->
        <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textColor="@android:color/white"
        android:text="
手勢兩點觸碰 - 圖片縮放滑動範例"
        android:textStyle="bold|italic"
        />
   
        <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
   
                <LinearLayout
                android:id="@+id/btn_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:layout_alignParentBottom="true"
                >
                         <!--
切換狀態的CheckBox-->
                         <CheckBox
                         android:id="@+id/normal"
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content"
                         android:layout_weight="1"
                         android:text="
ImageView型式"
                         />
                         <!--
切換狀態的CheckBox-->
                         <CheckBox
                         android:id="@+id/change"
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content"
                         android:layout_weight="1"
                         android:text="
手勢兩點觸碰型式"
                          />
               
                </LinearLayout>
                <!--
當前狀態的顯示文字-->
                <TextView
                android:id="@+id/state_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_above="@id/btn_layout"
                android:layout_centerInParent="true"
                />
   
                <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_above="@id/state_text"
                >
                        <!--
正常的ImageView,初始狀態為隱藏-->
                        <ImageView
                        android:id="@+id/image" 
                        android:layout_width="match_parent" 
                        android:layout_height="match_parent" 
                        android:src="@drawable/image"
                        android:visibility="gone"
                        />
                        <!--
覆寫的ImageView,初始狀態為隱藏-->
                        <
您的PackageName.ScaleImage
                        android:id="@+id/scale_image" 
                        android:layout_width="match_parent" 
                        android:layout_height="match_parent" 
                        android:src="@drawable/image"
                        android:visibility="gone"
                        />
               
                </RelativeLayout>
   
        </RelativeLayout>

</LinearLayout>

4.
最後,我們只要在Activity(.java)裡做狀態與圖片的切換就完成囉,MainActivity程式碼如下 :

public class MainActivity extends Activity
{
        private static final String NORMAL = "
圖片狀態 : 不可縮放滑動";
        private static final String CHANGE = "
圖片狀態 : 可以縮放滑動";
       
        private TextView mStateText;
        private ImageView mImage;
        private ScaleImage mScaleImage;
        private CheckBox mNormal,mChange;
       
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.main);
               
                mImage = (ImageView) findViewById(R.id.image);
                mScaleImage = (ScaleImage) findViewById(R.id.scale_image);
                mStateText = (TextView) findViewById(R.id.state_text);

                mNormal = (CheckBox) findViewById(R.id.normal);
                mNormal.setOnClickListener(new OnClickListener()
                {
                        @Override
                        public void onClick(View v)
                        {
                                //
點選NormalmNormal狀態為勾選,mChange狀態為未勾選
                                mNormal.setChecked(true);
                                mChange.setChecked(false);
                                //
輸入目前狀態文字
                                mStateText.setText(NORMAL);
                                //
原始圖片顯示,縮放圖片隱藏
                                mImage.setVisibility(View.VISIBLE);
                                mScaleImage.setVisibility(View.GONE);
                        }
                });

                mChange = (CheckBox) findViewById(R.id.change);
                mChange.setOnClickListener(new OnClickListener()
                {
                        @Override
                        public void onClick(View v)
                        {
                                //
點選ChangemNormal狀態為未勾選,mChange狀態為勾選
                                mNormal.setChecked(false);
                                mChange.setChecked(true);
                                //
輸入目前狀態文字
                                mStateText.setText(CHANGE);
                                //
原始圖片隱藏,縮放圖片顯示
                                mImage.setVisibility(View.GONE);
                                mScaleImage.setVisibility(View.VISIBLE);
                        }
                });
               
                //
預設為顯示原始圖片與狀態文字
                mNormal.setChecked(true);
                mImage.setVisibility(View.VISIBLE);
                mStateText.setText(NORMAL);
        }
}

將以上程式碼寫入後就可以針對圖片(ImageView)進行滑動與縮放的功能囉,小黑人範例裡特別添加原本的ImageView無法滑動與縮放與覆寫的ImageView可以滑動與縮放來做兩者的比較,大家可以試試看加入到程式裡面來運用手勢動作進行圖片(ImageView)的滑動與縮放吧。

小黑人範例畫面如下 :






謝謝大家,如有任何問題都可以和小黑人一起交流討論!

☆小黑人☆

8 則留言:

  1. 小黑人你好! 請問我想要讀取照片的地理位置(經緯度) 要怎麼匯入呢?
    另外我如果我想要選取多張照片顯示,要怎麼做呢?麻煩你了 謝謝!

    回覆刪除
  2. 你好
    我依你的例子做但唯一差別
    你的例子在 public class MainActivity extends Activity
    setContentView(R.layout.main);
    而我用android studio 所產生 public class MainActivity extends Activity
    setContentView(R.layout.activity_main);
    差別在 main 及 activity_main
    但我將 activity_maine改成main 卻出現紅字錯誤

    謝謝

    回覆刪除
    回覆
    1. 你引用的xml不相同

      刪除
    2. 正如樓上大哥講的引用的問題而已,若堅持使用R.layout.activity_main ,只需要在小黑人大哥MainActivity.java中
      setContentView(R.layout.main);改成setContentView(R.layout.activity_main);
      就可以work了,
      Layout可以玩很多東西唷,如果有興趣可以去了解LayoutInflater及AlertDialog.Builder的關係,網路上很多文章,一起加油 :)

      刪除
  3. 是否有辦法做出類似FB聊天圓球一樣,一張小圖滑動時在MotionEvent.ACTION_UP階段做速度控制的慣性移動?

    回覆刪除
  4. 想請問如果不需要Check Box來執行縮放圖片的程式該怎麼做?(多圖放同一頁面)

    回覆刪除
  5. 放大缩小原始碼我要購買

    回覆刪除
  6. 請問下圖片縮放完要定義畫筆涂鴉怎麼處理

    回覆刪除

謝謝大家支持,有任何問題都可以和小黑人一起討論!