前言
因?yàn)楣卷?xiàng)目要實(shí)現(xiàn)一個(gè)效果让腹,在ScrollView沒(méi)有向下滾動(dòng)時(shí)焦读,下拉(未重寫(xiě)前下拉是沒(méi)有任何效果的)放大頂部的圖片仓蛆,當(dāng)時(shí)去網(wǎng)上找了,記得以前見(jiàn)過(guò)很多這樣的控件的蝌诡,現(xiàn)在卻找半天也很難找到一個(gè)徒河,好不容易找到了2個(gè),發(fā)現(xiàn)效果都和需求上面的效果有偏差送漠,最后沒(méi)有辦法只能是自己寫(xiě)了顽照,花費(fèi)了半天時(shí)間研究出來(lái)了,同時(shí)為了記錄實(shí)現(xiàn)思路闽寡,所以就有了此文章
效果
實(shí)現(xiàn)思路
攔截ScrollView的觸摸滑動(dòng)事件(ACTION_MOVE),記錄下當(dāng)前事件y軸坐標(biāo)代兵,判斷當(dāng)前ScrollView的Y軸滾動(dòng)進(jìn)度(getScrollY)是否等于0,等于0就與上次事件記錄的位置進(jìn)行對(duì)比爷狈,如果為正數(shù)就放大(X軸是從左往右植影,Y軸是從上往下,所以下拉時(shí)本次事件的Y軸會(huì)大于上次事件的Y軸)涎永,每次事件都通過(guò)設(shè)置ImageView的高度來(lái)放大圖片控件(本來(lái)想用屬性動(dòng)畫(huà)的思币,但是因?yàn)槊總€(gè)事件放大的比例非常小,所以最后就沒(méi)使用羡微,直接通過(guò)修改屬性來(lái)實(shí)現(xiàn))谷饿,同時(shí)記錄從開(kāi)始到現(xiàn)在事件位置一共偏移了多少,當(dāng)偏移量大于最大值的妈倔,就停止放大并將偏移量設(shè)置為最大值博投,當(dāng)偏移量小于0時(shí),則將偏移量設(shè)置為0盯蝴,同時(shí)不再繼續(xù)攔截事件毅哗。注意被放大的圖片需要設(shè)置scaleType為centerCrop听怕,這樣當(dāng)圖片高度發(fā)生變化時(shí),圖片內(nèi)容才會(huì)跟著大虑绵,當(dāng)然其他幾種模式有些模式也能跟著放大尿瞭,但是具體可以自己去測(cè)試,我就不去測(cè)試了翅睛,畢竟我已經(jīng)達(dá)到我要的效果了
好了声搁,廢話少說(shuō),先貼代碼宏所,再對(duì)代碼進(jìn)行說(shuō)明
代碼
package wang.raye.library;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ScrollView;
/**
* 重寫(xiě)讓ScrollView有滾動(dòng)監(jiān)聽(tīng)(23以前是沒(méi)有滾動(dòng)監(jiān)聽(tīng)的)
* 攔截touch事件酥艳,讓其支持下拉放大圖片
* Created by Raye on 2016/6/11.
*/
public class ZoomScrollView extends ScrollView {
private View zoomView;
/** 記錄上次事件的Y軸*/
private float mLastMotionY;
/** 記錄一個(gè)滾動(dòng)了多少距離摊溶,通過(guò)這個(gè)來(lái)設(shè)置縮放*/
private int allScroll = -1;
/** 控件原本的高度*/
private int height = 0;
/** 被放大的控件id*/
private int zoomId;
/** 最大放大多少像素*/
private int maxZoom;
/** 滾動(dòng)監(jiān)聽(tīng)*/
private ScrollViewListener scrollViewListener = null;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
allScroll -= 25;
if(allScroll < 0){
allScroll = 0;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) zoomView.getLayoutParams();
lp.height = (int) (height + allScroll/2);
zoomView.setLayoutParams(lp);
if(allScroll != 0){
handler.sendEmptyMessageDelayed(1,10);
}else{
allScroll = -1;
}
}
};
public ZoomScrollView(Context context) {
super(context);
}
public ZoomScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public ZoomScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ZoomScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
zoomView = findViewById(zoomId);
}
private void init(AttributeSet attrs){
TypedArray t = getContext().obtainStyledAttributes(attrs, R.styleable.ObservableScrollView);
zoomId = t.getResourceId(R.styleable.ObservableScrollView_zoomId,-1);
maxZoom = t.getDimensionPixelOffset(R.styleable.ObservableScrollView_maxZoom,0);
}
public void setScrollViewListener(ScrollViewListener scrollViewListener) {
this.scrollViewListener = scrollViewListener;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if(zoomView == null || maxZoom == 0){
return super.dispatchTouchEvent(event);
}
final int action = event.getAction();
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
if(allScroll != -1){
handler.sendEmptyMessageDelayed(1,10);
}
return super.dispatchTouchEvent(event);
}
switch (action) {
case MotionEvent.ACTION_MOVE: {
final float y = event.getY();
final float diff, absDiff;
diff = y - mLastMotionY;
mLastMotionY = y;
absDiff = Math.abs(diff);
if(allScroll >= 0 && absDiff > 1){
allScroll += diff;
if(allScroll < 0){
allScroll = 0;
}else if(allScroll > maxZoom){
allScroll = maxZoom;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) zoomView.getLayoutParams();
lp.height = (int) (height + allScroll/2);
zoomView.setLayoutParams(lp);
if(allScroll == 0){
allScroll = -1;
}
return false;
}
if (isReadyForPullStart()) {
if (absDiff > 0 ) {
if (diff >= 1f && isReadyForPullStart()) {
mLastMotionY = y;
allScroll = 0;
height = zoomView.getHeight();
return true;
}
}
}
break;
}
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if(allScroll != -1){
Log.i("ScrollView","onTouchEvent");
return false;
}
return super.onTouchEvent(ev);
}
/**
* 返回是否可以開(kāi)始放大
* @return
*/
protected boolean isReadyForPullStart() {
return getScrollY() == 0;
}
@Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
if (scrollViewListener != null) {
scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
}
}
public interface ScrollViewListener {
void onScrollChanged(ZoomScrollView scrollView, int x, int y, int oldx, int oldy);
}
}
重要點(diǎn)爬骤,從上往下
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
allScroll -= 25;
if(allScroll < 0){
allScroll = 0;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) zoomView.getLayoutParams();
lp.height = (int) (height + allScroll/2);
zoomView.setLayoutParams(lp);
if(allScroll != 0){
handler.sendEmptyMessageDelayed(1,10);
}else{
allScroll = -1;
}
}
};
這里是當(dāng)ACTION_UP事件發(fā)生時(shí),如果圖片還在放大狀態(tài)莫换,就模擬動(dòng)畫(huà)效果霞玄,吧圖片縮放回去,當(dāng)然是可以用屬性動(dòng)畫(huà)的拉岁,只是我之前沒(méi)用屬性動(dòng)畫(huà)坷剧,所以這里也直接用這個(gè)了
@Override
protected void onFinishInflate() {
super.onFinishInflate();
zoomView = findViewById(zoomId);
}
這里是當(dāng)控件從xml中初始化完成的生命周期方法,在這里我們找到被放大的圖片控件
private void init(AttributeSet attrs){
TypedArray t = getContext().obtainStyledAttributes(attrs, R.styleable.ObservableScrollView);
zoomId = t.getResourceId(R.styleable.ObservableScrollView_zoomId,-1);
maxZoom = t.getDimensionPixelOffset(R.styleable.ObservableScrollView_maxZoom,0);
}
這段代碼相信很容易看懂喊暖,就是獲取2個(gè)自定義屬性惫企,一個(gè)是被放大的圖片控件id,一個(gè)是最大的放大像素
最主要的地方陵叽,事件攔截
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if(zoomView == null || maxZoom == 0){
return super.dispatchTouchEvent(event);
}
final int action = event.getAction();
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
if(allScroll != -1){
handler.sendEmptyMessageDelayed(1,10);
}
return super.dispatchTouchEvent(event);
}
switch (action) {
case MotionEvent.ACTION_MOVE: {
final float y = event.getY();
final float diff, absDiff;
diff = y - mLastMotionY;
mLastMotionY = y;
absDiff = Math.abs(diff);
if(allScroll >= 0 && absDiff > 1){
allScroll += diff;
if(allScroll < 0){
allScroll = 0;
}else if(allScroll > maxZoom){
allScroll = maxZoom;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) zoomView.getLayoutParams();
lp.height = (int) (height + allScroll/2);
zoomView.setLayoutParams(lp);
if(allScroll == 0){
allScroll = -1;
}
return false;
}
if (isReadyForPullStart()) {
if (absDiff > 0 ) {
if (diff >= 1f && isReadyForPullStart()) {
mLastMotionY = y;
allScroll = 0;
height = zoomView.getHeight();
return true;
}
}
}
break;
}
}
return super.dispatchTouchEvent(event);
}
詳細(xì)說(shuō)明
if(zoomView == null || maxZoom == 0){
return super.dispatchTouchEvent(event);
}
當(dāng)控件為空和最大放大像素為0 的時(shí)候狞尔,不進(jìn)行事件攔截
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
if(allScroll != -1){
handler.sendEmptyMessageDelayed(1,10);
}
return super.dispatchTouchEvent(event);
}
當(dāng)事件取消和手指松開(kāi)時(shí),判斷當(dāng)前偏移量(allScroll )是否回到了最初狀態(tài)-1巩掺,如果沒(méi)有說(shuō)明圖片沒(méi)有縮放偏序,要縮放回去
case MotionEvent.ACTION_MOVE: {
final float y = event.getY();
final float diff, oppositeDiff, absDiff;
diff = y - mLastMotionY;
mLastMotionY = y;
absDiff = Math.abs(diff);
if( allScroll >= 0 && absDiff > 1){
allScroll += diff;
if(allScroll < 0){
allScroll = 0;
}else if(allScroll > maxZoom){
allScroll = maxZoom;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) zoomView.getLayoutParams();
lp.height = (int) (height + allScroll/2);
zoomView.setLayoutParams(lp);
if(allScroll == 0){
allScroll = -1;
}
return false;
}
if (isReadyForPullStart()) {
if (absDiff > 0 ) {
if (diff >= 1f && isReadyForPullStart()) {
mLastMotionY = y;
allScroll = 0;
height = zoomView.getHeight();
return true;
}
}
}
break;
}
攔截移動(dòng)事件,每次記錄下Y軸坐標(biāo)胖替,當(dāng)滾動(dòng)為0的時(shí)候研儒,就計(jì)算與上次坐標(biāo)的偏移量,大于0就開(kāi)始放大独令,每次放大總偏移值的二分之一端朵,因?yàn)槊看畏糯罂偲浦档男Ч淮蠛每矗瑫r(shí)判斷總偏移值是否大于最大偏移值燃箭,大于就設(shè)置總偏移值為最大值逸月,相當(dāng)于停止放大。如果小于0遍膜,就把總偏移值設(shè)置為0碗硬,并且重置偏移值的為-1瓤湘,-1的時(shí)候,就不會(huì)攔截事件
@Override
public boolean onTouchEvent(MotionEvent ev) {
if(allScroll != -1){
Log.i("ScrollView","onTouchEvent");
return false;
}
return super.onTouchEvent(ev);
}
重寫(xiě)onTouchEvent恩尾,當(dāng)偏移值不是-1的時(shí)候弛说,說(shuō)明圖片在進(jìn)行放大或縮放,這時(shí)候不能讓ScrollView滾動(dòng)翰意,所以需要把onTouchEvent攔截掉
protected boolean isReadyForPullStart() {
return getScrollY() == 0;
}
獲取當(dāng)前ScrollView的滾動(dòng)位置木人,是0的時(shí)候才可以開(kāi)始放大圖片
最后說(shuō)兩句
控件中還有個(gè)監(jiān)聽(tīng),那個(gè)不用管冀偶,那個(gè)是為了獲取滾動(dòng)位置來(lái)設(shè)置標(biāo)題欄透明度的醒第,跟本文內(nèi)容無(wú)關(guān),所以就不詳細(xì)說(shuō)明了进鸠。當(dāng)然這個(gè)自定義控件只是為了實(shí)現(xiàn)我項(xiàng)目中需求的效果稠曼,很簡(jiǎn)陋,實(shí)現(xiàn)方法也很簡(jiǎn)單客年,所以歡迎高手前來(lái)指點(diǎn)霞幅。需要效果圖demo的請(qǐng)點(diǎn)擊demo github地址,另外同時(shí)也歡迎大家吐槽交流(QQ群:123965382)