1. 程式人生 > 實用技巧 >Android仿微信圖片詳情頁面,可下拉關閉頁面

Android仿微信圖片詳情頁面,可下拉關閉頁面

話不多說,直接上效果圖

imagereview.gif

ImageReview
在這個庫裡面用到了第三方的縮放圖片檢視庫

1、要實現效果圖的方案,需要操作重寫ViewPager的事件處理。
2、下滑透明的方案可以使用設定背景來實現

下拉縮放的效果需要重寫onInterceptTouchEvent和onTouchEvent,
在onInterceptTouchEvent中不攔截down事件,並且在move事件中判斷是否滿足下滑縮放的條件;
在onTouchEvent中實現具體的縮放以及透明度變化的效果;
同時在ViewPager的滑動要與下滑縮放區分開,因此需要監聽OnPageChangeListener,並在onTouchEvent判斷是否ViewPager在滑動中

public class DragViewPager extends ViewPager implements View.OnClickListener {
    public static final int STATUS_NORMAL = 0;//正常瀏覽狀態
    public static final int STATUS_MOVING = 1;//滑動狀態
    public static final int STATUS_RESETTING = 2;//返回中狀態
    public static final String TAG = "DragViewPager";


    public static final float MIN_SCALE_SIZE = 0.3f;//最小縮放比例
    public static final int BACK_DURATION = 300;//ms
    public static final int DRAG_GAP_PX = 50;

    private int currentStatus = STATUS_NORMAL;
    private int currentPageStatus;

    private float mDownX;
    private float mDownY;
    private float screenHeight;

    /**
     * 要縮放的View
     */
    private View currentShowView;
    /**
     * 滑動速度檢測類
     */
    private VelocityTracker mVelocityTracker;
    private IAnimClose iAnimClose;

    public void setIAnimClose(IAnimClose iAnimClose) {
        this.iAnimClose = iAnimClose;
    }

    public DragViewPager(Context context) {
        super(context);
        init(context);
    }

    public DragViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public void init(Context context) {
        screenHeight = ScreenUtils.getScreenHeight(context);
        setBackgroundColor(Color.BLACK);
        addOnPageChangeListener(new OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            }

            @Override
            public void onPageSelected(int position) {

            }

            @Override
            public void onPageScrollStateChanged(int state) {
                currentPageStatus = state;
            }
        });
    }


    public void setCurrentShowView(View currentShowView) {
        this.currentShowView = currentShowView;
        if (this.currentShowView != null) {
            this.currentShowView.setOnClickListener(this);
        }
    }



    //配合SubsamplingScaleImageView使用,根據需要攔截ACTION_MOVE
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (getAdapter() instanceof ImagePagerAdapter) {
            ImagePagerAdapter adapter = ((ImagePagerAdapter) getAdapter());
            SubsamplingScaleImageView mImage = (SubsamplingScaleImageView) adapter.getItem(getCurrentItem()).getView().findViewById(R.id.image);
            switch (ev.getAction()){
                case MotionEvent.ACTION_DOWN:
                    Log.e("jc","onInterceptTouchEvent:ACTION_DOWN");
                    mDownX = ev.getRawX();
                    mDownY = ev.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e("jc","onInterceptTouchEvent:ACTION_MOVE");
                    if (mImage.getCenter() != null && mImage.getCenter().y <= mImage.getHeight() / mImage.getScale() / 2) {
                        Log.e("jc","onInterceptTouchEvent:ACTION_MOVE");
                        int deltaX = Math.abs((int) (ev.getRawX() - mDownX));
                        int deltaY = (int) (ev.getRawY() - mDownY);
                        if (deltaY > DRAG_GAP_PX && deltaX <= DRAG_GAP_PX) {//往下移動超過臨界,左右移動不超過臨界時,攔截滑動事件
                            return true;
                        }
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e("jc","onInterceptTouchEvent:ACTION_UP");
                    break;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (currentStatus == STATUS_RESETTING)
            return false;
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                mDownX = ev.getRawX();
                mDownY = ev.getRawY();
                addIntoVelocity(ev);
                break;
            case MotionEvent.ACTION_MOVE:
                addIntoVelocity(ev);
                int deltaY = (int) (ev.getRawY() - mDownY);
                //手指往上滑動
                if (deltaY <= DRAG_GAP_PX && currentStatus != STATUS_MOVING)
                    return super.onTouchEvent(ev);
                //viewpager不在切換中,並且手指往下滑動,開始縮放
                if (currentPageStatus != SCROLL_STATE_DRAGGING && (deltaY > DRAG_GAP_PX || currentStatus == STATUS_MOVING)) {
                    moveView(ev.getRawX(), ev.getRawY());
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (currentStatus != STATUS_MOVING)
                    return super.onTouchEvent(ev);
                final float mUpX = ev.getRawX();
                final float mUpY = ev.getRawY();

                float vY = computeYVelocity();//鬆開時必須釋放VelocityTracker資源
                if (vY >= 1200 || Math.abs(mUpY - mDownY) > screenHeight / 4) {
                    //下滑速度快,或者下滑距離超過螢幕高度的一半,就關閉
                    if (iAnimClose != null) {
                        iAnimClose.onPictureRelease(currentShowView);
                    }
                } else {
                    resetReviewState(mUpX, mUpY);
                }
                break;
        }

        return super.onTouchEvent(ev);
    }

    //返回瀏覽狀態
    private void resetReviewState(final float mUpX, final float mUpY) {
        currentStatus = STATUS_RESETTING;
        if (mUpY != mDownY) {
            ValueAnimator valueAnimator = ValueAnimator.ofFloat(mUpY, mDownY);
            valueAnimator.setDuration(BACK_DURATION);
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float mY = (float) animation.getAnimatedValue();
                    float percent = (mY - mDownY) / (mUpY - mDownY);
                    float mX = percent * (mUpX - mDownX) + mDownX;
                    moveView(mX, mY);
                    if (mY == mDownY) {
                        mDownY = 0;
                        mDownX = 0;
                        currentStatus = STATUS_NORMAL;
                    }
                }
            });
            valueAnimator.start();
        } else if (mUpX != mDownX) {
            ValueAnimator valueAnimator = ValueAnimator.ofFloat(mUpX, mDownX);
            valueAnimator.setDuration(BACK_DURATION);
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float mX = (float) animation.getAnimatedValue();
                    float percent = (mX - mDownX) / (mUpX - mDownX);
                    float mY = percent * (mUpY - mDownY) + mDownY;
                    moveView(mX, mY);
                    if (mX == mDownX) {
                        mDownY = 0;
                        mDownX = 0;
                        currentStatus = STATUS_NORMAL;
                    }
                }
            });
            valueAnimator.start();
        } else if (iAnimClose != null)
            iAnimClose.onPictureClick();
    }


    //移動View
    private void moveView(float movingX, float movingY) {
        if (currentShowView == null)
            return;
        currentStatus = STATUS_MOVING;
        float deltaX = movingX - mDownX;
        float deltaY = movingY - mDownY;
        float scale = 1f;
        float alphaPercent = 1f;
        if (deltaY > 0) {
            scale = 1 - Math.abs(deltaY) / screenHeight;
            alphaPercent = 1 - Math.abs(deltaY) / (screenHeight / 2);
        }

        ViewHelper.setTranslationX(currentShowView, deltaX);
        ViewHelper.setTranslationY(currentShowView, deltaY);
        scaleView(scale);
        setBackgroundColor(getBlackAlpha(alphaPercent));
    }

    //縮放View
    private void scaleView(float scale) {
        scale = Math.min(Math.max(scale, MIN_SCALE_SIZE), 1);
        ViewHelper.setScaleX(currentShowView, scale);
        ViewHelper.setScaleY(currentShowView, scale);
    }


    private int getBlackAlpha(float percent) {
        percent = Math.min(1, Math.max(0, percent));
        int intAlpha = (int) (percent * 255);
        return Color.argb(intAlpha,0,0,0);
    }

    private void addIntoVelocity(MotionEvent event) {
        if (mVelocityTracker == null)
            mVelocityTracker = VelocityTracker.obtain();
        mVelocityTracker.addMovement(event);
    }


    private float computeYVelocity() {
        float result = 0;
        if (mVelocityTracker != null) {
            mVelocityTracker.computeCurrentVelocity(1000);
            result = mVelocityTracker.getYVelocity();
            releaseVelocity();
        }
        return result;
    }

    private void releaseVelocity() {
        if (mVelocityTracker != null) {
            mVelocityTracker.clear();
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    @Override
    public void onClick(View v) {
        if (iAnimClose != null) {
            iAnimClose.onPictureClick();
        }
    }


    public interface IAnimClose {
        void onPictureClick();

        void onPictureRelease(View view);
    }


}

需要進行縮放的View我這裡在Adapter中添加回調設定,並且adapter可以實現更新的效果

public class ImagePagerAdapter extends FragmentStatePagerAdapter {
    private DragViewPager mPager;
    private ArrayList<Fragment> mFragmentList;

    public ImagePagerAdapter(FragmentManager fm, List<String> datas,DragViewPager pager) {
        super(fm);
        mPager=pager;
        mPager.setAdapter(this);
        updateData(datas);
    }

    public void updateData(List<String> dataList) {
        ArrayList<Fragment> fragments = new ArrayList<>();
        for (int i = 0, size = dataList.size(); i < size; i++) {
            final ImageDetailFragment fragment = ImageDetailFragment.newInstance(dataList.get(i));
            fragment.setOnImageListener(new ImageDetailFragment.OnImageListener() {
                @Override
                public void onInit() {
                    View view = fragment.getView();
                    mPager.setCurrentShowView(view);
                }
            });
            fragments.add(fragment);
        }
        setViewList(fragments);
    }

    private void setViewList(ArrayList<Fragment> fragmentList) {
        if (mFragmentList != null) {
            mFragmentList.clear();
        }
        mFragmentList = fragmentList;
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return mFragmentList==null?0:mFragmentList.size();
    }

    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }


}

要實現透明度變化的效果還需要對activity設定theme

<style name="translucent" parent="AppTheme">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
</style>
<activity android:name=".ui.ImagePagerActivity"
            android:theme="@style/translucent"/>

在需要使用的頁面只需要呼叫即可

ImagePagerActivity.startImagePage(MainActivity.this,
                        urls,pos,recyclerView.getLayoutManager().findViewByPosition(pos));

在整個demo中採用的轉場動畫,需要設定共享元素,因此在需要使用的頁面需要設定如下:

首先設定轉場動畫的共享元素,因為跳轉和返回時都會呼叫onMapSharedElements,需要判斷bundle是否為空,bundle會在返回的時候在onActivityReenter獲取到

//設定轉場動畫的共享元素,因為跳轉和返回時都會呼叫onMapSharedElements,需要判斷bundle是否為空
        setExitSharedElementCallback(new SharedElementCallback() {
            @Override
            public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
                if (bundle!=null){
                    int index = bundle.getInt(ImagePagerActivity.STATE_POSITION,0);
                    sharedElements.clear();
                    sharedElements.put("img", recyclerView.getLayoutManager().findViewByPosition(index));
                    bundle=null;
                }
            }
        });

其次在返回的時候獲取資料bundle

@Override
    public void onActivityReenter(int resultCode, Intent data) {
        super.onActivityReenter(resultCode, data);
        bundle = data.getExtras();
        int currentPosition = bundle.getInt(ImagePagerActivity.STATE_POSITION,0);
        //做相應的滾動
        recyclerView.scrollToPosition(currentPosition);
        //暫時延遲 Transition 的使用,直到我們確定了共享元素的確切大小和位置才使用
        //postponeEnterTransition後不要忘記呼叫startPostponedEnterTransition
        ActivityCompat.postponeEnterTransition(this);
        recyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                recyclerView.getViewTreeObserver().removeOnPreDrawListener(this);
                // TODO: figure out why it is necessary to request layout here in order to get a smooth transition.
                recyclerView.requestLayout();
                //共享元素準備好後呼叫startPostponedEnterTransition來恢復過渡效果
                ActivityCompat.startPostponedEnterTransition(MainActivity.this);
                return true;
            }
        });
    }

完整的呼叫頁面的程式碼如下:

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;

    private MainAdapter mainAdapter;

    //圖片集合
    private ArrayList<String> urls;

    //存放返回時當前頁碼
    private Bundle bundle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        urls=new ArrayList<>();
        //為了顯示效果,重複添加了三次
        urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524477979306&di=3eb07e9302606048abe13d7b6a2bc601&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201406%2F12%2F20140612211118_YYXAC.jpeg");
        urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524133463580&di=1315bc4db30999f00b89ef79c3bb06e5&imgtype=0&src=http%3A%2F%2Fpic36.photophoto.cn%2F20150710%2F0005018721870517_b.jpg");
        urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524133463575&di=6221f21bcb761675c5d161ebc53d5948&imgtype=0&src=http%3A%2F%2Fimg5.duitang.com%2Fuploads%2Fitem%2F201410%2F03%2F20141003112442_AkkuH.thumb.700_0.jpeg");

        urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524477979306&di=3eb07e9302606048abe13d7b6a2bc601&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201406%2F12%2F20140612211118_YYXAC.jpeg");
        urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524133463580&di=1315bc4db30999f00b89ef79c3bb06e5&imgtype=0&src=http%3A%2F%2Fpic36.photophoto.cn%2F20150710%2F0005018721870517_b.jpg");
        urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524133463575&di=6221f21bcb761675c5d161ebc53d5948&imgtype=0&src=http%3A%2F%2Fimg5.duitang.com%2Fuploads%2Fitem%2F201410%2F03%2F20141003112442_AkkuH.thumb.700_0.jpeg");

        urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524477979306&di=3eb07e9302606048abe13d7b6a2bc601&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201406%2F12%2F20140612211118_YYXAC.jpeg");
        urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524133463580&di=1315bc4db30999f00b89ef79c3bb06e5&imgtype=0&src=http%3A%2F%2Fpic36.photophoto.cn%2F20150710%2F0005018721870517_b.jpg");
        urls.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524133463575&di=6221f21bcb761675c5d161ebc53d5948&imgtype=0&src=http%3A%2F%2Fimg5.duitang.com%2Fuploads%2Fitem%2F201410%2F03%2F20141003112442_AkkuH.thumb.700_0.jpeg");
        initView();
    }

    private void initView() {
        recyclerView = (RecyclerView) findViewById(R.id.recycler);
        recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
        mainAdapter = new MainAdapter(urls);
        mainAdapter.setOnItemClickListener(new MainAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(int pos) {
                ImagePagerActivity.startImagePage(MainActivity.this,
                        urls,pos,recyclerView.getLayoutManager().findViewByPosition(pos));
            }
        });
        recyclerView.setAdapter(mainAdapter);

        //設定轉場動畫的共享元素,因為跳轉和返回都會呼叫,需要判斷bundle是否為空
        setExitSharedElementCallback(new SharedElementCallback() {
            @Override
            public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
                if (bundle!=null){
                    int index = bundle.getInt(ImagePagerActivity.STATE_POSITION,0);
                    sharedElements.clear();
                    sharedElements.put("img", recyclerView.getLayoutManager().findViewByPosition(index));
                    bundle=null;
                }
            }
        });
    }


    //返回的時候獲取資料
    @Override
    public void onActivityReenter(int resultCode, Intent data) {
        super.onActivityReenter(resultCode, data);
        bundle = data.getExtras();
        int currentPosition = bundle.getInt(ImagePagerActivity.STATE_POSITION,0);
        //做相應的滾動
        recyclerView.scrollToPosition(currentPosition);
        //暫時延遲 Transition 的使用,直到我們確定了共享元素的確切大小和位置才使用
        //postponeEnterTransition後不要忘記呼叫startPostponedEnterTransition
        ActivityCompat.postponeEnterTransition(this);
        recyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                recyclerView.getViewTreeObserver().removeOnPreDrawListener(this);
                // TODO: figure out why it is necessary to request layout here in order to get a smooth transition.
                recyclerView.requestLayout();
                //共享元素準備好後呼叫startPostponedEnterTransition來恢復過渡效果
                ActivityCompat.startPostponedEnterTransition(MainActivity.this);
                return true;
            }
        });
    }
}

需要檢視完整程式碼點選這裡,喜歡的請給個star,謝謝



作者:JC_Hou
連結:https://www.jianshu.com/p/3c6384973db6
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

來源:生雞蛋