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

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;//mspublic 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() {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}@Overridepublic void onPageSelected(int position) {}@Overridepublic 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@Overridepublic 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);}@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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();}//移动Viewprivate 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));}//缩放Viewprivate 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;}}@Overridepublic 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() {@Overridepublic 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();}@Overridepublic int getCount() {return mFragmentList==null?0:mFragmentList.size();}public int getItemPosition(Object object) {return POSITION_NONE;}@Overridepublic 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() {@Overridepublic 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
@Overridepublic 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后不要忘记调用startPostponedEnterTransitionActivityCompat.postponeEnterTransition(this);recyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {@Overridepublic 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;@Overrideprotected 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() {@Overridepublic void onItemClick(int pos) {ImagePagerActivity.startImagePage(MainActivity.this,urls,pos,recyclerView.getLayoutManager().findViewByPosition(pos));}});recyclerView.setAdapter(mainAdapter);//设置转场动画的共享元素,因为跳转和返回都会调用,需要判断bundle是否为空setExitSharedElementCallback(new SharedElementCallback() {@Overridepublic 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;}}});}//返回的时候获取数据@Overridepublic 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后不要忘记调用startPostponedEnterTransitionActivityCompat.postponeEnterTransition(this);recyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {@Overridepublic 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
到这里就结束啦。
评论
