用過多米音樂的都市知道, 這個(gè)UI可以上下滑動(dòng),作用嘛---無(wú)聊中可以劃劃解解悶,這被錘子公司老羅稱謂為“情懷”,其實(shí)叫“情味”更合適。嘿嘿.如今挪動(dòng)互聯(lián)網(wǎng)開展這么迅速,市場(chǎng)上已不再是那早期隨便敲個(gè)APP放上架就能具有幾十萬(wàn)用戶的階段了.近來(lái)蘋果公司,為了怕android下載量趕超蘋果商店,大勢(shì)宣稱:(第 500 億個(gè)下載應(yīng)用的用戶就能夠獲得 10,000 美元的 iTunes 禮品卡,除此之外,緊隨第 500 億以后的前 50 名用戶也可以獲得 500 美元的禮品卡.至于挪動(dòng)開展趨勢(shì),我想搞挪動(dòng)IT的人心里都比擬清楚,扯遠(yuǎn)了).其實(shí)應(yīng)用UI殊效是應(yīng)用中很大的一部分,如果同樣功能的兩款軟件,一個(gè)功能好點(diǎn)如“網(wǎng)易新聞”,另外一個(gè)略微差點(diǎn)如“新浪新聞”,用戶的你毫無(wú)疑難確定會(huì)選擇網(wǎng)易客戶端.總結(jié)就是“操作性”對(duì)于產(chǎn)品起著至關(guān)重要的因素.
接下來(lái)我們看下如何實(shí)現(xiàn),首先聲明,這個(gè)實(shí)現(xiàn)的方式不是很好,我這里只是提出一個(gè)解決方案,大家可以根據(jù)自己的想法進(jìn)行創(chuàng)新.
道理:RelativeLayout+自定義ScrollView.
我們大致看下布局結(jié)構(gòu)如圖:
其實(shí)也沒什么技術(shù)含量,我簡(jiǎn)單介紹下:紅色代表的是背景照片,綠色的代表自定義ScrollView,粉色是代表你要編輯的透明區(qū)域.也不過多解釋,想必大家都明確,我們還是來(lái)看代碼吧。
由于屬于情懷殊效(沒有具體的回調(diào)事件要求),那么就沒有必要自定義監(jiān)聽,回調(diào)處理,我直接把要處理的UI注入到自定義控件中,這樣她方便我也方便.
在此說明一下,前面部分實(shí)現(xiàn)中有誤,但是也希望您仔細(xì)品讀,相信您必定可以學(xué)到一些知識(shí)的。
首先我們將背景圖片和頂部線條注入到該控件中。接著我們看onTouchEvent事件,因?yàn)橹潦贾两K都是她在起作用.
current_Top = initTop = imageView.getTop();
current_Bottom = initBottom = imageView.getBottom();
lineUp_current_Top = line_up_top = line_up.getTop();
lineUp_current_Bottom = line_up_bottom = line_up.getBottom();
break;
case MotionEvent.ACTION_UP:
/** 回縮動(dòng)畫 **/
if (isNeedAnimation()) {
animation();
}
isMoveing = false;
touchY = 0;// 手指松開要?dú)w0.
break;
/***
* 消除出第一次挪動(dòng)計(jì)算,因?yàn)榈谝淮螣o(wú)法得知deltaY的高度, 然而我們也要進(jìn)行初始化,就是第一次挪動(dòng)的時(shí)候讓滑動(dòng)距離歸0.
* 以后記載精確了就正常執(zhí)行.
*/
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "isMoveing=" + isMoveing);
touchY = ev.getY();
float deltaY = touchY - initTouchY;// 滑動(dòng)距離
Log.e(TAG, "deltaY=" + deltaY);
/** 過濾: **/
if (deltaY < 0 && inner.getTop() <= 0) {
return;
}
// 當(dāng)滾動(dòng)到最上或者最下時(shí)就不會(huì)再滾動(dòng),這時(shí)挪動(dòng)布局
isNeedMove();
if (isMoveing) {
// 初始化頭部矩形
if (normal.isEmpty()) {
// 保存正常的布局位置
normal.set(inner.getLeft(), inner.getTop(),
inner.getRight(), inner.getBottom());
}
// 挪動(dòng)布局(手勢(shì)挪動(dòng)的1/3)
float inner_move_H = deltaY / 5;
inner.layout(normal.left, (int) (normal.top + inner_move_H),
normal.right, (int) (normal.bottom + inner_move_H));
/** image_bg **/
float image_move_H = deltaY / 10;
current_Top = (int) (initTop + image_move_H);
current_Bottom = (int) (initBottom + image_move_H);
imageView.layout(imageView.getLeft(), current_Top,
imageView.getRight(), current_Bottom);
/** line_up **/
float line_up_H = inner_move_H;
lineUp_current_Top = (int) (line_up_top + inner_move_H);
lineUp_current_Bottom = (int) (line_up_bottom + inner_move_H);
line_up.layout(line_up.getLeft(), lineUp_current_Top,
line_up.getRight(), lineUp_current_Bottom);
}
break;
default:
break;
}
}
MotionEvent.ACTION_DOWN:觸摸摁下獲得相應(yīng)的坐標(biāo).
MotionEvent.ACTION_MOVE:
里面有個(gè)方法isNeedMove。作用:我們滑動(dòng)的是ScrollView自身呢,還是我們自己模擬的那種滑動(dòng).
這里面用到最多的就是:view.layout(l, t, r, b);作用很簡(jiǎn)單不解釋。詳情請(qǐng)參看源碼.
MotionEvent.ACTION_UP:就是做些善后操作,主要看animation方法.
TranslateAnimation image_Anim = new TranslateAnimation(0, 0,
Math.abs(initTop - current_Top), 0);
image_Anim.setDuration(200);
imageView.startAnimation(image_Anim);
imageView.layout(imageView.getLeft(), (int) initTop,
imageView.getRight(), (int) initBottom);
// 開啟挪動(dòng)動(dòng)畫
TranslateAnimation inner_Anim = new TranslateAnimation(0, 0,
inner.getTop(), normal.top);
inner_Anim.setDuration(200);
inner.startAnimation(inner_Anim);
inner.layout(normal.left, normal.top, normal.right, normal.bottom);
/** line_up **/
TranslateAnimation line_up_Anim = new TranslateAnimation(0, 0,
Math.abs(line_up_top - lineUp_current_Top), 0);
line_up_Anim.setDuration(200);
line_up.startAnimation(line_up_Anim);
line_up.layout(line_up.getLeft(), line_up_top, line_up.getRight(),
line_up_bottom);
normal.setEmpty();
/** 動(dòng)畫執(zhí)行 **/
if (current_Top > initTop + 50 && turnListener != null)
turnListener.onTurn();
}
這里我要簡(jiǎn)單說明一下,因?yàn)槲以谶@里栽了有些時(shí)光.
比如:我們的背景圖片本來(lái)坐標(biāo)為:(0,-190,800,300),隨著手勢(shì)挪動(dòng)到(0,-100,800,390)挪動(dòng)了90像素,那么我們的TranslateAnimation應(yīng)當(dāng)如何寫呢?我之前總以為不就是末尾坐標(biāo)指向初始坐標(biāo)不就完了,結(jié)果你會(huì)發(fā)明,動(dòng)畫基本不起作用而是一閃而過。原因呢,動(dòng)畫參數(shù)弗成以為正數(shù).或許因?yàn)閯?dòng)畫是以(0,0)為參照物吧.因此要把動(dòng)畫寫成TranslateAnimation line_up_Anim = new TranslateAnimation(0, 0,Math.abs(-190- (-100)), 0);這樣我們所須要的動(dòng)畫效果就實(shí)現(xiàn)了.
但是新的問題又出現(xiàn)了:
當(dāng)你下拉到必定狀態(tài)后然后漸漸向上挪動(dòng),會(huì)發(fā)明挪動(dòng)的很快(沒有回縮的反響),而挪動(dòng)到最頂部的時(shí)候突然又出現(xiàn)反彈效果。這個(gè)效果固然不是我們所須要的那種。我們所須要的效果是:下拉到必定水平,然后反過來(lái)上拉的時(shí)候要漸漸的挪動(dòng)回到原點(diǎn)(中央位置)停止。如果是上拉的話,不要出現(xiàn)反彈效果,如果是下拉松開的話,出現(xiàn)反彈效果。
描述的有點(diǎn)亂,如果想知道具體效果的話,我提議你應(yīng)用下papa,其實(shí)海內(nèi)這些比擬優(yōu)秀的應(yīng)用UI都是抄襲國(guó)外的,如果你用facebook的話,就會(huì)發(fā)明,怎么啪啪的個(gè)人頁(yè)面長(zhǎng)的也忒像facebook了。請(qǐng)看下圖:
嘿嘿,不好意思,跑題了,針對(duì)上面出現(xiàn)的問題,我簡(jiǎn)單說明一下.
首先,比如我們手勢(shì)下拉了50像素,其實(shí)是使得自定義ScrollView的孩子也就是LinearLayout這個(gè)控件的top為50,而這個(gè)時(shí)候的getScrollY()的值仍為0,但是如果此時(shí)你停止下拉反而向上拉取的話,那么此時(shí)的getScrollY()會(huì)從0開始逐步增大,當(dāng)我們挪動(dòng)到頂部也就是將ScrollView挪動(dòng)到最底部,此時(shí)的isMoveing為true,所以你繼承上拉的話會(huì)出現(xiàn)反彈效果。
這個(gè)問題要如何解決呢,其實(shí)也不難,但是我糾結(jié)了好長(zhǎng)時(shí)光,也走了很多多少?gòu)澛?。在這里說明一下我的瞎跑路段以及疑難:當(dāng)時(shí)我就想,getScrollY()這么不聽話,我何必非要對(duì)ScrollView的孩子進(jìn)行操作呢,為何直接不對(duì)本控件執(zhí)行l(wèi)ayout(l,t,r,b)呢,后來(lái)就照著這個(gè)邏輯進(jìn)行update,終于更改了差不多了,糾結(jié)了問題再次出現(xiàn),在你下拉的時(shí)候?qū)crollView本身執(zhí)行l(wèi)ayout(l,t,r,b)這個(gè)方法可以實(shí)現(xiàn)反彈效果,但是此時(shí)你確無(wú)法進(jìn)行滑動(dòng)了,就是ScrollView本身的滑動(dòng)無(wú)緣無(wú)故的被禁止掉了.我懷疑是layout的時(shí)候參數(shù)弄錯(cuò)了。,后來(lái)仔細(xì)修改了下發(fā)明還是弗成以滑動(dòng),然后google了半天也杳無(wú)音訊,最后固然放棄,又回到了原點(diǎn)。接著揣摩。。。算是功夫不負(fù)有心人吧,最終想到了解決方案,希望對(duì)您有幫助。
還拿上面說到的那短話,比如我們手勢(shì)下拉了50像素,那么此時(shí)touch的距離也就是50像素,如果此時(shí)我們反向上拉的話,同樣是須要50像素回到最初的位置。說到這里我想大家都明確了。(首先我們要將操作離開,分為UP,DOWN,如果是DOWN的話,那么在下拉后執(zhí)行上拉的時(shí)候我們禁用掉自定義控件的滑動(dòng),而是通過手勢(shì)執(zhí)行l(wèi)ayout執(zhí)行這50像素.)
上面我們看部分代碼:
if (state == State.UP) {
deltaY = deltaY < 0 ? deltaY : 0;
isMoveing = false;
shutTouch = false;
} else if (state == state.DOWN) {
if (getScrollY() <= deltaY) {
shutTouch = true;
isMoveing = true;
}
deltaY = deltaY < 0 ? 0 : deltaY;
}
代碼很簡(jiǎn)單,不過多解釋了,不明確的話,仔細(xì)看下源碼確定就明確了。
touch 事件處理:
}
說明:如果返回值為true,作用:禁止ScrollView的滑動(dòng),此時(shí)的Touch事件還存哦?。?!如果對(duì)Touch事件比擬熟悉的同窗,相信以為我有點(diǎn)空話了,哈哈,我也是個(gè)小菜鳥,也卡在這里過。
最后呢,還有個(gè)小BUG,也就是那個(gè)頂部拉線,如果你讓ScrollView慣性滑動(dòng)的話,那么你會(huì)發(fā)明,頂部線條沒有追隨挪動(dòng),其實(shí)就是因?yàn)閼T性滑動(dòng)的時(shí)候我們是獲得不到getScrollY()的值得造成的,查了半天也沒有找到相關(guān)資料,這個(gè)問題就臨時(shí)就留在這里,有時(shí)光了在續(xù)。
這里我將源碼貼出來(lái):
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.ScrollView;
/**
* 自定義ScrollView
*
* @author jia
*
*/
public class PersonalScrollView extends ScrollView {
private final String TAG = PersonalScrollView.class.getSimpleName();
private View inner;// 孩子View
private float touchY;// 點(diǎn)擊時(shí)Y坐標(biāo)
private float deltaY;// Y軸滑動(dòng)的距離
private float initTouchY;// 初次點(diǎn)擊的Y坐標(biāo)
private boolean shutTouch = false;// 是不是關(guān)閉ScrollView的滑動(dòng).
private Rect normal = new Rect();// 矩形(這里只是個(gè)形式,只是用于判斷是不是須要?jiǎng)赢?)
private boolean isMoveing = false;// 是不是開始挪動(dòng).
private ImageView imageView;// 背景圖控件.
private View line_up;// 上線
private int line_up_top;// 上線的top
private int line_up_bottom;// 上線的bottom
private int initTop, initBottom;// 初始高度
private int current_Top, current_Bottom;// 拖動(dòng)時(shí)時(shí)高度。
private int lineUp_current_Top, lineUp_current_Bottom;// 上線
private onTurnListener turnListener;
private ImageView imageHeader;
public void setImageHeader(ImageView imageHeader) {
this.imageHeader = imageHeader;
}
// 狀態(tài):上部,下部,默認(rèn)
private enum State {
UP, DOWN, NOMAL
};
// 默認(rèn)狀態(tài)
private State state = State.NOMAL;
public void setTurnListener(onTurnListener turnListener) {
this.turnListener = turnListener;
}
public void setLine_up(View line_up) {
this.line_up = line_up;
}
// 注入背景圖
public void setImageView(ImageView imageView) {
this.imageView = imageView;
}
/***
* 構(gòu)造方法
*
* @param context
* @param attrs
*/
public PersonalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/***
* 根據(jù) XML 生成視圖工作實(shí)現(xiàn).該函數(shù)在生成視圖的最后調(diào)用,在所有子視圖添加完以后. 即使子類覆蓋了 onFinishInflate
* 方法,也應(yīng)當(dāng)調(diào)用父類的方法,使該方法得以執(zhí)行.
*/
@Override
protected void onFinishInflate() {
if (getChildCount() > 0) {
inner = getChildAt(0);
}
}
/** touch 事件處理 **/
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (inner != null) {
commOnTouchEvent(ev);
}
// ture:禁止控件本身的滑動(dòng).
if (shutTouch)
return true;
else
return super.onTouchEvent(ev);
}
/***
* 觸摸事件
*
* @param ev
*/
public void commOnTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
initTouchY = ev.getY();
current_Top = initTop = imageView.getTop();
current_Bottom = initBottom = imageView.getBottom();
if (line_up_top == 0) {
lineUp_current_Top = line_up_top = line_up.getTop();
lineUp_current_Bottom = line_up_bottom = line_up.getBottom();
}
break;
case MotionEvent.ACTION_UP:
/** 回縮動(dòng)畫 **/
if (isNeedAnimation()) {
animation();
}
if (getScrollY() == 0) {
state = State.NOMAL;
}
isMoveing = false;
touchY = 0;
shutTouch = false;
break;
/***
* 消除出第一次挪動(dòng)計(jì)算,因?yàn)榈谝淮螣o(wú)法得知deltaY的高度, 然而我們也要進(jìn)行初始化,就是第一次挪動(dòng)的時(shí)候讓滑動(dòng)距離歸0.
* 以后記載精確了就正常執(zhí)行.
*/
case MotionEvent.ACTION_MOVE:
touchY = ev.getY();
deltaY = touchY - initTouchY;// 滑動(dòng)距離
/** 對(duì)于初次Touch操作要判斷方位:UP OR DOWN **/
if (deltaY < 0 && state == state.NOMAL) {
state = State.UP;
} else if (deltaY > 0 && state == state.NOMAL) {
state = State.DOWN;
}
if (state == State.UP) {
deltaY = deltaY < 0 ? deltaY : 0;
isMoveing = false;
shutTouch = false;
/** line_up **/
lineUp_current_Top = (int) (line_up_top - getScrollY());
lineUp_current_Bottom = (int) (line_up_bottom - getScrollY());
Log.e(TAG, "top=" + getScrollY());
line_up.layout(line_up.getLeft(), lineUp_current_Top,
line_up.getRight(), lineUp_current_Bottom);
} else if (state == state.DOWN) {
if (getScrollY() <= deltaY) {
shutTouch = true;
isMoveing = true;
}
deltaY = deltaY < 0 ? 0 : deltaY;
}
if (isMoveing) {
// 初始化頭部矩形
if (normal.isEmpty()) {
// 保存正常的布局位置
normal.set(inner.getLeft(), inner.getTop(),
inner.getRight(), inner.getBottom());
}
// 挪動(dòng)布局(手勢(shì)挪動(dòng)的1/3)
float inner_move_H = deltaY / 5;
inner.layout(normal.left, (int) (normal.top + inner_move_H),
normal.right, (int) (normal.bottom + inner_move_H));
/** image_bg **/
float image_move_H = deltaY / 10;
current_Top = (int) (initTop + image_move_H);
current_Bottom = (int) (initBottom + image_move_H);
imageView.layout(imageView.getLeft(), current_Top,
imageView.getRight(), current_Bottom);
/** line_up **/
lineUp_current_Top = (int) (line_up_top + inner_move_H);
lineUp_current_Bottom = (int) (line_up_bottom + inner_move_H);
line_up.layout(line_up.getLeft(), lineUp_current_Top,
line_up.getRight(), lineUp_current_Bottom);
}
break;
default:
break;
}
}
/***
* 回縮動(dòng)畫
*/
public void animation() {
TranslateAnimation image_Anim = new TranslateAnimation(0, 0,
Math.abs(initTop - current_Top), 0);
image_Anim.setDuration(200);
imageView.startAnimation(image_Anim);
imageView.layout(imageView.getLeft(), (int) initTop,
imageView.getRight(), (int) initBottom);
// 開啟挪動(dòng)動(dòng)畫
TranslateAnimation inner_Anim = new TranslateAnimation(0, 0,
inner.getTop(), normal.top);
inner_Anim.setDuration(200);
inner.startAnimation(inner_Anim);
inner.layout(normal.left, normal.top, normal.right, normal.bottom);
/** line_up **/
TranslateAnimation line_up_Anim = new TranslateAnimation(0, 0,
Math.abs(line_up_top - lineUp_current_Top), 0);
line_up_Anim.setDuration(200);
line_up.startAnimation(line_up_Anim);
line_up.layout(line_up.getLeft(), line_up_top, line_up.getRight(),
line_up_bottom);
normal.setEmpty();
/** 動(dòng)畫執(zhí)行 **/
if (current_Top > initTop + 50 && turnListener != null)
turnListener.onTurn();
}
/** 是不是須要開啟動(dòng)畫 **/
public boolean isNeedAnimation() {
return !normal.isEmpty();
}
/***
* 執(zhí)行翻轉(zhuǎn)
*
* @author jia
*
*/
public interface onTurnListener {
/** 必須到達(dá)必定水平才執(zhí)行 **/
void onTurn();
}
}
效果圖:
界面有點(diǎn)丑陋,不過UI可以自己根據(jù)需求進(jìn)行調(diào)整.
新聞熱點(diǎn)
疑難解答
圖片精選