1. 程式人生 > >原來操控介面可以這麼簡單----安卓上下滑動縮放頂部圖片,左右滑動結束當前Activity,及View柔和回彈效果

原來操控介面可以這麼簡單----安卓上下滑動縮放頂部圖片,左右滑動結束當前Activity,及View柔和回彈效果

先上效果圖:

上傳圖片不能超過2M,費了好大勁。每一張gif動的有點快,將就看。

首先說原理:

為activity的xml檔案根佈局新增setOnTouchListener。上下滑動和左右滑動的所有操作都是在OnTouchListeneronTouch方法中實現的,通過計算上下左右滑動的距離來操作View的。

一共有兩個介面,第一個是LoginActivity,什麼用也沒有,就是在裡面的button,點選跳轉到MainActivity.MainActivity是主要的。

主要程式碼:

LoginActivity:

mButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
               startActivity(new Intent(this,MainActivity.class));

            }
        });



activity_main:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout

    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"

    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/img"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:scaleType="centerCrop"
        android:src="@drawable/xx"
        android:layout_gravity="center_horizontal"
        />
    <LinearLayout android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:background="@color/colorPrimary"
        >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Hello World!" />
    </LinearLayout>
</LinearLayout>

先獲取activity_main.xml的根佈局View,然後為它新增Touch事件監聽,之後所有的操作都會在其中進行。

root_ll = (LinearLayout) findViewById(R.id.activity_main);
root_ll.setOnTouchListener(new View.OnTouchListener() {
    @Override
public boolean onTouch(View v, MotionEvent event) {
        //程式碼在這裡
        return true;
    }
});
注意ontouch方法一定要返回true。

先說上下滑動縮放圖片:

分為兩部分,
下滑時:圖片會放大,寬度和高度都會變化,並且圖片和下面的helloworld區域存在一種類似圖層疊加透視的關係,仔細看效果圖,圖片和下面的區域並不是像鋪地板一樣,一塊和另一塊緊挨在一起。而是隨著圖片放大,這兩部分看起來像兩層,就好像下面的區域是覆蓋在圖片上一樣。
上滑時,圖片沒有縮放,並且高度變小,寬度保持不變。

佈局檔案:

關於圖片的縮放,只要在佈局檔案中設定
android:scaleType="centerCrop"既可。這樣我們在縮放的時候只要控制ImageView的寬高度就可以,圖片會自動追隨ImageView的變化而放大縮小。
佈局到這就結束了,回到程式碼。

第一步,得到ImageView正常時的寬高度:

ImageView img;
int width;
int height;
img = (ImageView) findViewById(R.id.img);
ViewTreeObserver viewTreeObserver=img.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    @Override
public void onGlobalLayout() {
        width = img.getMeasuredWidth();
        height = img.getMeasuredHeight();
        img.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }
});
應該知道這裡不能通過img.getWidth()方法獲得吧。因為這時候圖片還沒有繪製出來,得到的是0.
img.getViewTreeObserver().removeOnGlobalLayoutListener(this);
這一局要有,是在得到寬高度之後取消監聽,不然之後每當img屬性發生變化時都會執行這個方法。

第二部,在onTouch中開始操作:

整個觸控式螢幕幕的過程分為down,move,up三個階段。
在down的時候得到參考點:
if (event.getAction()==MotionEvent.ACTION_DOWN) {
    starty = event.getY();
    startx = event.getX();
}
然後在滑動的時候實時獲取x,y點,來縮放ImageView
if (event.getAction()==MotionEvent.ACTION_MOVE) {
    
        tw = (int) (width * ((event.getY() - starty) / 1000 + 1));
        th = (int) (height * ((event.getY() - starty) / 1600 + 1));
        if (th<=0) {
            th=1;
        }

        if (th < height) {
            layoutParams.width = width;
            layoutParams.height = th;
        } else {
            layoutParams.width = tw;
            layoutParams.height = th;
        }
        img.setLayoutParams(layoutParams);
    } 
這裡是根據上下滑動的距離來縮放,所以(event.getY() - starty) / 1000是得到上下滑動的距離,除以1000,只是降低一下幅度而已,也可以換成其它的數值,這樣得到的tw就是width放大了多少倍。特別需要注意,
th = (int) (height * ((event.getY() - starty) / 1600 + 1));這裡高度除以的是1600,要比1000大,正是這個不一樣實現了圖片看起來是圖層疊加的效果,如果我沒有說清的話,你可以把高度也設定成除以1000.看看不同就明白了。
獲得到縮放後的寬高後,和圖片的原始高度比較一下,if (th < height)就知道是上滑還是下滑,然後設定一下寬高度就可以了。

第三步,擡起手指時的回彈效果:

if (event.getAction()==MotionEvent.ACTION_UP) {
     if (th < height / 2) {
            back2Origin(width, th, width, 1);
        } else if (th < height) {
            back2Origin(width, th, width, height);
        } else {
            back2Origin(tw, th, width, height);
        }
這裡也有一個簡單的判斷,如果此時高度小於原始高度一半了,那就讓高度漸變到1,如果不是就回彈回原來的高度,這樣就可以實現上滑下滑的動畫效果,我覺得這裡的功臣就是
android:scaleType="centerCrop"這行。解決了圖層疊加,遮擋的這種效果問題。
回彈,我用到的是屬性動畫,後面拿出來。
這樣,上下滑動的效果就實現了。

左右滑動:

第一步,設定activity主題為透明:

左右滑動時我們會看到下面露出了之前的activity的介面,這是因為設定了當前的activity主題為透明,然後通過設定TranslutionX來實現左右滑動效果。
自定義一個透明樣式:
在styles.xml檔案中
<style name="myTransparent" parent="@style/AppTheme">
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
    <item name="windowNoTitle">true</item>
</style>
在manifest檔案中為MainActivity使用自定義的樣式:
<activity
android:name=".MainActivity"android:theme="@style/myTransparent"
>
</activity>
設定完畢,回到onTouch程式碼中。

第二步,實現左右滑動:

if (event.getAction()==MotionEvent.ACTION_MOVE) {   
root_ll.setTranslationX(currentTranslationX+(event.getX() - startx)/2f);
}
(event.getX() - startx)/2f是得到橫向的滑動距離,也是降低了一下幅度,currentTranslationX是root_ll當前的偏移數,
就這麼簡單的一設定,就可以實現activity左右不停偏移,露出之前的activity了。此時還沒有回彈效果,這裡也是用到了屬性動畫。一會說。
這裡還有一個小問題,看圖,頂部的狀態列並沒有隨著一起運動,體驗很差。
這就涉及到一個問題,就是我們為activity setContentView的xml並不是activity的全部,這只是activity佈局的一個子View而已,具體可以搜尋詳解。
所以這裡我們要得到activity真正的載入View,
View root=this.getWindow().getDecorView();這個root就可以了。

第三步,添加回彈效果:

if (event.getAction()==MotionEvent.ACTION_UP) {
    
        currentTranslationX = root.getTranslationX();
        handleTranslationX();
}
handleTranslationX()方法來處理的回彈動畫,這樣左右滑動也監聽實現了。

組合:

單個實現上下滑或左右簡單,但是組裝在onTouch方法中,就會有問題了,我們可以簡單的判斷一下:
if (event.getAction()==MotionEvent.ACTION_MOVE) {    
        if (Math.abs(event.getY() - starty )>=Math.abs( event.getX() - startx)) {
            vertical=true;
        } else {
            vertical=false;
    }
if (vertical) {
    Log.i("xx", "" + event.getY());
    tw = (int) (width * ((event.getY() - starty) / 1000 + 1));
    th = (int) (height * ((event.getY() - starty) / 1600 + 1));
    if (th<=0) {
        th=1;
    }

    if (th < height) {
        layoutParams.width = width;
        layoutParams.height = th;
    } else {
        layoutParams.width = tw;
        layoutParams.height = th;
    }
    img.setLayoutParams(layoutParams);
} else {
    root.setTranslationX(currentTranslationX+(event.getX() - startx)/2f);
}

vertical:boolean值,表示上下滑動,先根據方向的距離大小判斷出是橫向還是縱向,然後根據virtical來分離操作。這樣會有問題的,可以實驗一下,會發現因為move是在不斷呼叫的,所有即使一開始是左右滑,一旦手勢上下了virtical也會被置true,就會導致一會執行左右滑,一會執行上下滑。所以我們需要一個類似網路會話中session的東西,來告訴ontouch,一旦開始時判斷出是哪個方向,就在這次不斷的滑動當中,始終忽略另一個方向的滑動,直到擡起手指,這次滑動結束。
所以新增另一個boolean值thisTime判斷:
if (event.getAction()==MotionEvent.ACTION_MOVE) {
    if (!thisTime) {
        thisTime=true;
        if (Math.abs(event.getY() - starty )>=Math.abs( event.getX() - startx)) {
            vertical=true;

        } else {
            vertical=false;

        }
    }
這一段程式碼,一旦判斷出virtical的值,同時thisTime=true,這樣在之後的滑動當中就不會再執行判斷virtical的程式碼,virtical值就一直不會改變了。解決了一會上下滑 一會左右滑的問題。
直到擡起手指:
if (event.getAction()==MotionEvent.ACTION_UP) {    
    thisTime=false;
}
表示這個滑動結束了,這樣當下次再按下手指滑動時,就又會重新判斷一次virtical,一次新的單向滑動開始了。

回彈動畫:

用的是屬性動畫中的ValueAnimator。我就直接貼程式碼了。解釋在註釋裡。具體的屬性動畫詳情也可以搜尋。
/**
 * 
 * @param fromw 起始寬度
* @param fromh 起始高度
* @param tow 結束寬度
* @param toh 結束高度
*/
public void back2Origin(final int fromw, final int fromh, final int tow, int toh) {
        final int ws=tow-fromw;
    final int hs=toh-fromh;
//建立一個ValueAnimator,讓他的值從0變化到100.
    ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 100);
//為valueAnimator新增一個事件監聽,每次數值變化時都會執行這個方法。
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
public void onAnimationUpdate(ValueAnimator animation) {
//animation.getAnimatedValue(),取得此時的數值(0-100)
            layoutParams.height=fromh+(int)((int)animation.getAnimatedValue()/100f*hs);
            layoutParams.width=fromw+(int)((int)animation.getAnimatedValue()/100f*ws);
            img.setLayoutParams(layoutParams);
        }
    });
//設定這個動畫的時常
    valueAnimator.setDuration(500);
//開始執行動畫
    valueAnimator.start();
}

/**
 * 這裡判斷右滑的距離,如果超過寬度四分之一了,就向右滑到底,並結束
* 如果不是就回彈
*/
public void handleTranslationX() {
    Point p = new Point();
//取得螢幕的寬度,存放在point中
    getWindowManager().getDefaultDisplay().getSize(p);
    if (Math.abs(currentTranslationX) > p.x / 4f) {
        slideWithFinish(p.x);
    } else {
        slideWithFinish(0f);
    }
}

/**
 * 左右滑動的動畫
* @param endX 結束時的偏移量
*/
public void slideWithFinish(final float endX) {
    ValueAnimator valueAnimator = ValueAnimator.ofFloat(currentTranslationX, endX);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
public void onAnimationUpdate(ValueAnimator animation) {
            root.setTranslationX((float)animation.getAnimatedValue());
//這裡判斷一下動畫結束時是否是finish當前activity
            if ((float)animation.getAnimatedValue()==endX) {
                if (endX > currentTranslationX) {
                    MainActivity.this.finish();
                } else {
                    currentTranslationX = endX;
                }
            }

        }
    });
    valueAnimator.setDuration(500);
    valueAnimator.start();
}

到這就結束了,其實很簡單吧。程式碼寫的不完善,比如左右滑時可以向左滑,一些小情況沒有判斷。寫的不對的地方請大家指教,如果幫到你了,
給我一個贊,小小的頂我一下吧!謝謝啦୧༼ʘ̆ںʘ̆༽୨
程式碼在這裡