请选择 进入手机版 | 继续访问电脑版

酷炫的Android交互动画和视觉效果:高仿音悦台播放页面

发表于 2017-1-15 14:52:00 显示全部楼层 0 758

新版的音悦台 APP  播放页面交互非常有意思,可以把播放器往下拖动,这个页面透明渐变,然后到底部可以左右拖动关闭播放器,然后点击视频列表有个页面弹出来的效果,十分炫酷,于是我自己动手实现了这个交互炫酷的播放器页面。
.

wKiom1h7HHSxEebqAAB410M3hPs194.jpg-wh_651x-s_3110548420.jpg

wKiom1h7HHSxEebqAAB410M3hPs194.jpg-wh_651x-s_3110548420.jpg

.
1.废话不多说,直接演示实现效果
.
1.1.点击某个视频,然后手指上下拖动,播放器做尺寸比例的渐变,视频相关信息做透明度渐变
.

wKiom1h7HNCgezWnAAc7OToXrtc752.gif

wKiom1h7HNCgezWnAAc7OToXrtc752.gif

.
1.2.播放器只有在底部的时候,才能左右拖动,此时播放器做透明度渐变,拖动一定范围可以关闭播放器;然后它只有在原始位置的一小段距离内可以往上拖动
.

wKiom1h7HOHiUvBfAAdTIx1m0Sc550.gif

wKiom1h7HOHiUvBfAAdTIx1m0Sc550.gif

.
1.3.点击视频列表的时候,若是上次视频是左右拖动关闭的话,会有个弹起播放页面的效果;若是返回键和返回箭头则无效果
.

wKioL1h7HPDh6JUsAAeIBplRgjw193.gif

wKioL1h7HPDh6JUsAAeIBplRgjw193.gif

.
2.实现的思路讲解
.
毫无疑问,需要自定义一个容器,然后处理它的触摸事件,对它的子 View 进行不同的处理。触摸事件的处理使用 ViewDragHelper  是再适合不过了,然后你需要实现容器 onMeasure 和 onLayout,由于使用了 ViewDragHelper,有些坑在代码解析的时候就会讲解。
.
播放页面是用新的 Activity 还仅仅是当前 Activity  的View的问题,由于播放器缩小到底部的时候,用户是可以滑动视频列表的,所以我个人认为就是在当前 Activity  放置一个自定义容器即可,因此为了效率考虑你可以用 ViewStub 来懒加载处理,这里方便演示我就直接 View 的形式了。
.
3.代码解析
.
3.1.需要的变量
.

wKiom1h7HRXzM87DAAKGO0NTKkg666.jpg

wKiom1h7HRXzM87DAAKGO0NTKkg666.jpg

.
3.2.初始化做 ViewDragHelper 的初始化,然后 post 拿到两个子 View,这里强制规定只能有两个子元素
.

wKioL1h7HSLxMVytAAHtnyyi2fs449.jpg

wKioL1h7HSLxMVytAAHtnyyi2fs449.jpg

.
3.3. ViewDragHelper 的回调需要做的事情比较多,在 mFlexView 拖动的时候需要同时设置 mFlexView 和  mFollowView 的相应变化效果,在 mFlexView 释放的时候需要处理关闭或收起等效果
.

wKioL1h7HTHAzYvJAAbaQ5CuScQ641.jpg

wKioL1h7HTHAzYvJAAbaQ5CuScQ641.jpg

.
3.4.接下来是处理测量和定位,我们实现的排列效果类似 LinearLayout 垂直排列的效果,这里被 measureChildWithMargins  的 heightUse 摆了一道;onLayout 的时候在位置缓存不为空的时候直接定位是因为 ViewDragHelper  在处理触摸事件子元素在做一些平移之类的,若是有元素更新了 UI 会导致重新 Layout,例如我的播放器在更新时间的 TextView 时就会如此,因此在  FlexCallback 的 onViewPositionChanged 方法记录位置,在重新 Layout 时恢复位置即可,这个也坑了好久
.

wKiom1h7HUTwqth5AARQc0wc0C8706.jpg

wKiom1h7HUTwqth5AARQc0wc0C8706.jpg

.
3.5.触摸事件的处理,由于缩放不会影响 mFlexView 真实宽高,ViewDragHelper 仍然会阻断 mFlexView  的真实宽高的区域,所以这里判断手指是否落在 mFlexView 视觉上的范围内,在才去调 ViewDragHelper 的  shouldInterceptTouchEvent 方法
.

wKiom1h7HVTg2XkGAAJVm5cSl0s505.jpg

wKiom1h7HVTg2XkGAAJVm5cSl0s505.jpg

.
3.6.在 computeScroll 中,若是 mIsClosing 为 true,即关闭的整个平移执行完毕了,通知回调事件
.

wKiom1h7HWDDhOH5AAGn2Aczlfo294.jpg

wKiom1h7HWDDhOH5AAGn2Aczlfo294.jpg

.
3.7.容器实现了,接下来我们继承 YytLayout 实现播放器页面的组合控件即可,再封装一些常用的方法,这里使用的是大名鼎鼎的 Ijkplayer  实现的播放器,屏蔽了 IjkVideoView 的触摸事件自己处理了;顺带一提,为了实现播放器 Controller 跟随拖动缩放的效果,放弃了常用的  PopupWindow 实现的思路,IjkController 直接是添加到 IjkVideoView 中的,要不弹窗实现跟随播放器太麻烦了
.
/**
  • * Created by Oubowu on 2016/12/27 17:32.  * 仿音悦台播放页面的具体实现,组合控件的形式
  • */ public class YytPlayer extends YytLayout {
  •     private IjkController mIjkController;     private IjkVideoView mIjkVideoView;
  •     private ImageView mIvAvatar;     private TextView mTvName;
  •     private TextView mTvTime;     private TextView mTvTitle;
  •     private TextView mTvDesc;     private RecyclerView mYytRecyclerView;
  •     private VideoListAdapter mVideoListAdapter;     public YytPlayer(Context context, AttributeSet attrs) {
  •         super(context, attrs);         init(context, attrs);
  •     }     private void init(Context context, AttributeSet attrs) {
  •         // 继承YytLayout并且通过merge标签减少层级来实现组合控件         LayoutInflater.from(context).inflate(R.layout.yyt_player, this, true);
  •         setOnLayoutStateListener(new OnLayoutStateListener() {             @Override
  •             public void onClose() {                 setVisibility(View.INVISIBLE);
  •                 mIjkVideoView.release(true);             }
  •         });         mIjkVideoView = (IjkVideoView) findViewById(R.id.ijk_player_view);
  •         final int scaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();         mIjkVideoView.setOnTouchListener(new OnTouchListener() {
  •             float mDownX = 0;             float mDownY = 0;
  •             boolean mClickCancel;             @Override
  •             public boolean onTouch(View v, MotionEvent event) {                 float x = event.getX();
  •                 float y = event.getY();                 switch (event.getAction()) {
  •                     case MotionEvent.ACTION_DOWN:                         mDownX = x;
  •                         mDownY = y;                         break;
  •                     case MotionEvent.ACTION_MOVE:                         if (Math.abs(mDownX - x) > scaledTouchSlop || Math.abs(mDownY - y) > scaledTouchSlop) {
  •                             mClickCancel = true;                         }
  •                         break;                     case MotionEvent.ACTION_UP:
  •                         if (!mClickCancel && Math.abs(mDownX - x)                             // 点击事件偶尔失效,只好这里自己解决了
  •                             if (isHorizontalDragEnable()) {                                 expand();
  •                             } else {                                 mIjkVideoView.toggleMediaControlsVisibility();
  •                             }                         }
  •                         mClickCancel = false;                         break;
  •                     case MotionEvent.ACTION_CANCEL:                         mClickCancel = false;
  •                         break;                 }
  •                 return true;             }
  •         });         mIvAvatar = (ImageView) findViewById(R.id.iv_avatar);
  •         mTvName = (TextView) findViewById(R.id.tv_name);         mTvTime = (TextView) findViewById(R.id.tv_time);
  •         mTvTitle = (TextView) findViewById(R.id.tv_title);         mTvDesc = (TextView) findViewById(R.id.tv_desc);
  •         mVideoListAdapter = new VideoListAdapter();         mVideoListAdapter.setOnItemClickCallback(new OnItemClickCallback() {
  •             @Override             public void onClick(View view, int position) {
  •                 int pos = (Integer) view.getTag();                 VideoSummary summary = mVideoListAdapter.getData().get(pos);
  •                 playVideo(mVideoListAdapter.getData(), summary);             }
  •         });         mYytRecyclerView = (RecyclerView) findViewById(R.id.yyt_recycler_view);
  •         GridLayoutManager gridLayoutManager = new GridLayoutManager(context, 2, LinearLayoutManager.VERTICAL, false);         mYytRecyclerView.setLayoutManager(gridLayoutManager);
  •         mYytRecyclerView.setNestedScrollingEnabled(false);         mYytRecyclerView.addItemDecoration(new VideoListItemDecoration(context));
  •         mYytRecyclerView.setAdapter(mVideoListAdapter);     }
  •     // 播放视频     private void playVideo(String path, String name) {
  •         try {             if (mIjkController == null) {
  •                 IjkMediaPlayer.loadLibrariesOnce(null);                 IjkMediaPlayer.native_profileBegin("libijkplayer.so");
  •                 mIjkController = new IjkController(mIjkVideoView, name);                 mIjkController.setOnViewStateListener(new IjkController.OnViewStateListener() {
  •                     @Override                     public void onBackPress() {
  •                         stop();                     }
  •                 });                 mIjkVideoView.setMediaController(mIjkController);
  •                 mIjkVideoView.setOnPreparedListener(new IMediaPlayer.OnPreparedListener() {                     @Override
  •                     public void onPrepared(IMediaPlayer mp) {                         mIjkVideoView.start();
  •                     }                 });
  •                 mIjkVideoView.setOnErrorListener(new IMediaPlayer.OnErrorListener() {                     @Override
  •                     public boolean onError(IMediaPlayer mp, int what, int extra) {                         Toast.makeText(getContext(), "视频播放出错了╮(╯Д╰)╭", Toast.LENGTH_SHORT).show();
  •                         return true;                     }
  •                 });             } else {
  •                 // 重新设置视频名字                 mIjkController.setVideoName(name);
  •             }             // 设置这个TextureView播放器缩放就正常了
  •             mIjkVideoView.setRender(IjkVideoView.RENDER_TEXTURE_VIEW);             // 因为每次setRender都会移除view再添加,为了缩放效果这里控制器是添加到IjkVideoView中的,所以这里也要重新添加才能在IjkVideoView的最上面
  •             mIjkController.updateControlView();             // 显示加载条
  •             mIjkController.showProgress();             // 播放视频
  •             mIjkVideoView.setVideoURI(Uri.parse(path));         } catch (UnsatisfiedLinkError e) {
  •             e.printStackTrace();             Toast.makeText(getContext(), "你的CPU是" + Build.CPU_ABI + ",当前播放器使用的编译版本" + BuildConfig.FLAVOR + "不匹配!", Toast.LENGTH_LONG).show();
  •         }     }
  •     /**      * 显示布局,并且播放视频
  •      *      * @param data    视频列表,用于播放页面下面的列表布局
  •      * @param summary 播放的视频信息      */
  •     public void playVideo(List data, VideoSummary summary) {         // 拿到数据,设置到播放的布局的相关信息
  •         Glide.with(getContext()).load(summary.mTopicImg).transform(new GlideCircleTransform(getContext())).into(mIvAvatar);         mTvName.setText(summary.mTopicName);
  •         mTvTime.setText(summary.mPtime);         mTvTitle.setText(Html.fromHtml(summary.mTitle));
  •         if (summary.mDescription.isEmpty()) {             mTvDesc.setText(summary.mTopicDesc);
  •         } else {             mTvDesc.setText(Html.fromHtml(summary.mDescription));
  •         }         // 设置YytLayout可见,并且展开
  •         setVisibility(View.VISIBLE);         expand();
  •         mVideoListAdapter.setData(data);         mVideoListAdapter.setItemWidth(mYytRecyclerView.getWidth() / 2);
  •         mVideoListAdapter.notifyDataSetChanged();         // 播放视频
  •         playVideo(summary.mMp4HdUrl == null ? summary.mMp4Url : summary.mMp4HdUrl, summary.mTitle);     }
  •     // 开始播放     public void start() {
  •         if (mIjkVideoView != null && !mIjkVideoView.isPlaying()) {             mIjkVideoView.start();
  •         }     }
  •     // 暂停播放     public void pause() {
  •         if (mIjkVideoView != null && mIjkVideoView.isPlaying()) {             mIjkVideoView.pause();
  •         }     }
  •     // 停止播放     public void stop() {
  •         setVisibility(View.INVISIBLE);         if (mIjkVideoView != null) {
  •             mIjkVideoView.release(true);         }
  •     }     public boolean isShowing() {
  •         return getVisibility() == VISIBLE;     }
  • } [/ol]4.总结
    .
    说难也不难,就是各种抠细节需要脑洞,各位不妨看到好玩的交互自己打开脑洞一下,接下来可能要实现下 UC 浏览器播放器的效果,感觉也是非常有意思。
    .
    出处:安卓巴士Android开发者门户
  • 温馨提示:请勿恶意灌水,一次扣10学分,发现三次以上封号!
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    admin

    管理员

    活跃度

    学分

    19万

    积分
    Ta的主页 发消息
    !zhankai!

    Archiver|手机版|小黑屋|句号网 ( 沪ICP备12036357号-3 )

    GMT+8, 2022-11-27 16:46

    Powered by Discuz! X3.4

    © 2001-2020 Comsenz Inc.

    打开微信扫一扫