Android仿微信图片详情页面,可下拉关闭页面

龙旋

共 18760字,需浏览 38分钟

 ·

2021-07-17 07:01

效果图:



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; } }); }}


源码地址:

https://github.com/JohnsonHou/ImageReview


到这里就结束啦。

浏览 28
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报