原來操控介面可以這麼簡單----安卓上下滑動縮放頂部圖片,左右滑動結束當前Activity,及View柔和回彈效果
阿新 • • 發佈:2019-01-08
先上效果圖:
上傳圖片不能超過2M,費了好大勁。每一張gif動的有點快,將就看。
首先說原理:
為activity的xml檔案根佈局新增setOnTouchListener。上下滑動和左右滑動的所有操作都是在OnTouchListener的onTouch方法中實現的,通過計算上下左右滑動的距離來操作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(); }
到這就結束了,其實很簡單吧。程式碼寫的不完善,比如左右滑時可以向左滑,一些小情況沒有判斷。寫的不對的地方請大家指教,如果幫到你了,
給我一個贊,小小的頂我一下吧!謝謝啦୧༼ʘ̆ںʘ̆༽୨
程式碼在這裡