赞
踩
现在很多全屏的视频播放器现在都有这样的功能:左边上下滑动调节亮度,右边上下滑动调节音量,左右滑动调节快进快退,双击控制暂停播放。实现这样的功能并不难,本文分享一下实现经验。
本实现采用GestureDetector来处理输入的手势,它的介绍可以看我的GestureDetector全面分析,在这里就不详细讲它的用法了。对于GestureDetector的回调,我们还要把它封装才能区分出那些上下左右的手势,所以这里继承一个RelativeLayout来封装它们。下面只介绍了具体实现思路,想开具体细节的可以进入demo查看。
主要是把onScroll()
滑动回调分成3个部分:音量、手势和快进快退,所以最后开放给外部的接口是这样的:
/** * 用于提供给外部实现的视频手势处理接口 */ public interface VideoGestureListener { //亮度手势,手指在Layout左半部上下滑动时候调用 public void onBrightnessGesture(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY); //音量手势,手指在Layout右半部上下滑动时候调用 public void onVolumeGesture(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY); //快进快退手势,手指在Layout左右滑动的时候调用 public void onFF_REWGesture(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY); //单击手势,确认是单击的时候调用 public void onSingleTapGesture(MotionEvent e); //双击手势,确认是双击的时候调用 public void onDoubleTapGesture(MotionEvent e); //按下手势,第一根手指按下时候调用 public void onDown(MotionEvent e); //快进快退执行后的松开时候调用 public void onEndFF_REW(MotionEvent e); }
为了给onScroll()
分成3个部分,这里采用一个小小的状态模式,给它定义4个状态:NONE,VOLUME,BRIGHTNESS,FF_REW。只有NONE状态才能进入其他状态,其它状态一旦进入了不可切换,这样就保证了用户划着音量的时候不会突然就平移就改变了进度:
public class VideoPlayerOnGestureListener extends GestureDetector.SimpleOnGestureListener { //... @Override public boolean onDown(MotionEvent e) { Log.d(TAG, "onDown: "); //每次按下都重置为NONE mScrollMode = NONE; if (mVideoGestureListener != null) { mVideoGestureListener.onDown(e); } return true; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { Log.d(TAG, "onScroll: e1:" + e1.getX() + "," + e1.getY()); Log.d(TAG, "onScroll: e2:" + e2.getX() + "," + e2.getY()); Log.d(TAG, "onScroll: X:" + distanceX + " Y:" + distanceY); switch (mScrollMode) { case NONE: Log.d(TAG, "NONE: "); //offset是让快进快退不要那么敏感的值 if (Math.abs(distanceX) - Math.abs(distanceY) > offsetX) { mScrollMode = FF_REW; } else { if (e1.getX() < getWidth() / 2) { mScrollMode = BRIGHTNESS; } else { mScrollMode = VOLUME; } } break; case VOLUME: if (mVideoGestureListener != null) { mVideoGestureListener.onVolumeGesture(e1, e2, distanceX, distanceY); } Log.d(TAG, "VOLUME: "); break; case BRIGHTNESS: if (mVideoGestureListener != null) { mVideoGestureListener.onBrightnessGesture(e1, e2, distanceX, distanceY); } Log.d(TAG, "BRIGHTNESS: "); break; case FF_REW: if (mVideoGestureListener != null) { mVideoGestureListener.onFF_REWGesture(e1, e2, distanceX, distanceY); } hasFF_REW = true; Log.d(TAG, "FF_REW: "); break; } return true; } //... }
然后在RelativeLayout里面使用这个VideoPlayerOnGestureListener
,就让它们绑定了,只要在Activity里面使用这个RelativeLayout,就可以使用前面的VideoGestureListener
接口的回调了。
public class VideoGestureRelativeLayout extends RelativeLayout { //... public VideoGestureRelativeLayout(Context context) { super(context); init(context); } public VideoGestureRelativeLayout(Context context, AttributeSet attrs) { super(context, attrs); init(context); } private void init(Context context){ mOnGestureListener = new VideoPlayerOnGestureListener(this); mGestureDetector = new GestureDetector(context,mOnGestureListener); //取消长按,不然会影响滑动 mGestureDetector.setIsLongpressEnabled(false); setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { //实现快进快退松开时候的回调 if (event.getAction() == MotionEvent.ACTION_UP){ if (hasFF_REW){ if (mVideoGestureListener != null) { mVideoGestureListener.onEndFF_REW(event); } hasFF_REW = false; } } //监听触摸事件 return mGestureDetector.onTouchEvent(event); } }); } //... }
由于GestureDetector没有滑动之后松开的回调,这里在onTouch()
方补一个回调。
来到这里,接口已经做好了,用户的手势我们都收到相应的回调了,然后我们要做的是定义收到这些回调的时候的操作。
在这里我做了一个比较丑的中间显示框,里面包含着一个ImageView和一个ProgressBar,默认延时一秒后消失:
/** * Author: yanzhikai * Description: 中间用于显示状态的Layout * Email: yanzhikai_yjk@qq.com */ public class ShowChangeLayout extends RelativeLayout { private static final String TAG = "gesturetest"; private ImageView iv_center; private ProgressBar pb; private HideRunnable mHideRunnable; private int duration = 1000; public ShowChangeLayout(Context context) { super(context); init(context); } public ShowChangeLayout(Context context, AttributeSet attrs) { super(context, attrs); init(context); } private void init(Context context){ LayoutInflater.from(context).inflate(R.layout.show_change_layout,this); iv_center = (ImageView) findViewById(R.id.iv_center); pb = (ProgressBar) findViewById(R.id.pb); mHideRunnable = new HideRunnable(); ShowChangeLayout.this.setVisibility(GONE); } //显示 public void show(){ setVisibility(VISIBLE); removeCallbacks(mHideRunnable); postDelayed(mHideRunnable,duration); } //设置进度 public void setProgress(int progress){ pb.setProgress(progress); Log.d(TAG, "setProgress: " +progress); } //设置持续时间 public void setDuration(int duration) { this.duration = duration; } //设置显示图片 public void setImageResource(int resource){ iv_center.setImageResource(resource); } //隐藏自己的Runnable private class HideRunnable implements Runnable{ @Override public void run() { ShowChangeLayout.this.setVisibility(GONE); } } }
首先是进行初始化:
public class MainActivity extends AppCompatActivity implements VideoGestureRelativeLayout.VideoGestureListener { private final String TAG = "gesturetestm"; private VideoGestureRelativeLayout ly_VG; private ShowChangeLayout scl; private AudioManager mAudioManager; private int maxVolume = 0; private int oldVolume = 0; private int newProgress = 0, oldProgress = 0; private BrightnessHelper mBrightnessHelper; private float brightness = 1; private Window mWindow; private WindowManager.LayoutParams mLayoutParams; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ly_VG = (VideoGestureRelativeLayout) findViewById(R.id.ly_VG); ly_VG.setVideoGestureListener(this); scl = (ShowChangeLayout) findViewById(R.id.scl); //初始化获取音量属性 mAudioManager = (AudioManager)getSystemService(Service.AUDIO_SERVICE); maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); //初始化亮度调节 mBrightnessHelper = new BrightnessHelper(this); //下面这是设置当前APP亮度的方法配置 mWindow = getWindow(); mLayoutParams = mWindow.getAttributes(); brightness = mLayoutParams.screenBrightness; } @Override public void onDown(MotionEvent e) { //每次按下的时候更新当前亮度和音量,还有进度 oldProgress = newProgress; oldVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); brightness = mLayoutParams.screenBrightness; if (brightness == -1){ //一开始是默认亮度的时候,获取系统亮度,计算比例值 brightness = mBrightnessHelper.getBrightness() / 255f; } } //... }
每次onDown()
都更新3个值的原因:onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
是每次Move事件位移大于1像素都会执行的(原因可看上一篇源码分析),distanceX
和distanceY
是两个MOVE之间的距离决定的,如果手指移动得比较慢,它们就会比较小,float转化成int很可能会被舍去小数点后的然后变成0,让用户不能慢慢通过滑动精准调控参数,所以要使用e2和e1的位移差来决定亮度等参数的变化大小,从而就要在onDown()
获取旧数值来作为起始点了,详细逻辑请看代码:
//... @Override public void onVolumeGesture(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { int value = ly_VG.getHeight()/maxVolume ; int newVolume = (int) ((e1.getY() - e2.getY())/value + oldVolume); mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,newVolume,AudioManager.FLAG_PLAY_SOUND); //要强行转Float类型才能算出小数点,不然结果一直为0 int volumeProgress = (int) (newVolume/Float.valueOf(maxVolume) *100); if (volumeProgress >= 50){ scl.setImageResource(R.drawable.volume_higher_w); }else if (volumeProgress > 0){ scl.setImageResource(R.drawable.volume_lower_w); }else { scl.setImageResource(R.drawable.volume_off_w); } scl.setProgress(volumeProgress); scl.show(); } @Override public void onBrightnessGesture(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { //下面这是设置当前APP亮度的方法 newBrightness += brightness; if (newBrightness < 0){ newBrightness = 0; }else if (newBrightness > 1){ newBrightness = 1; } mLayoutParams.screenBrightness = newBrightness; mWindow.setAttributes(mLayoutParams); scl.setProgress((int) (newBrightness * 100)); scl.setImageResource(R.drawable.brightness_w); scl.show(); } @Override public void onEndFF_REW(MotionEvent e) { makeToast("设置进度为" + newProgress); } @Override public void onFF_REWGesture(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { float offset = e2.getX() - e1.getX(); //根据移动的正负决定快进还是快退 if (offset > 0) { scl.setImageResource(R.drawable.ff); newProgress = (int) (oldProgress + offset/ly_VG.getWidth() * 100); if (newProgress > 100){ newProgress = 100; } }else { scl.setImageResource(R.drawable.fr); newProgress = (int) (oldProgress + offset/ly_VG.getWidth() * 100); if (newProgress < 0){ newProgress = 0; } } scl.setProgress(newProgress); scl.show(); } @Override public void onSingleTapGesture(MotionEvent e) { makeToast("SingleTap"); } @Override public void onDoubleTapGesture(MotionEvent e) { makeToast("DoubleTap"); } private void makeToast(String str){ Toast.makeText(this,str,Toast.LENGTH_SHORT).show(); } //...
这里没有处理多点触控,所以实际效果是触控手指只看第一根,后面落下的手指动作都忽视。
最后贴出调节系统亮度的辅助类:
/** * Author: yanzhikai * Description: 用于辅助调节亮度的类 * Email: yanzhikai_yjk@qq.com */ public class BrightnessHelper { private ContentResolver resolver; private int maxBrightness = 255; public BrightnessHelper(Context context){ resolver = context.getContentResolver(); } /* * 调整亮度范围 */ private int adjustBrightnessNumber(int brightness){ if (brightness < 0) { brightness = 0; } else if (brightness > 255) { brightness = 255; } return brightness; } /* * 关闭自动调节亮度 */ public void offAutoBrightness(){ try { if(Settings.System.getInt(resolver, Settings.System.SCREEN_BRIGHTNESS_MODE) == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) { Settings.System.putInt(resolver, Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); } } catch (Settings.SettingNotFoundException e) { e.printStackTrace(); } } /* * 获取系统亮度 */ public int getBrightness(){ return Settings.System.getInt(resolver, Settings.System.SCREEN_BRIGHTNESS, 255); } /* * 设置系统亮度,如果有设置了自动调节,请先调用offAutoBrightness()方法关闭自动调节,否则会设置失败 */ public void setSystemBrightness(int newBrightness){ Settings.System.putInt(resolver, Settings.System.SCREEN_BRIGHTNESS ,adjustBrightnessNumber(newBrightness)); } public int getMaxBrightness() { return maxBrightness; } /* * 设置当前APP的亮度 */ public void setAppBrightness(float brightnessPercent, Activity activity){ Window window = activity.getWindow(); WindowManager.LayoutParams layoutParams = window.getAttributes(); layoutParams.screenBrightness = brightnessPercent; window.setAttributes(layoutParams); } }
上面调节亮度选择的是控制当前APP的亮度,不改变系统亮度。其实这些控制的实现是多种多样的,我这里只是给出一种封装GestureDetector的思路和实现方法,各个步骤都说得挺清楚了,具体的细节大家可以根据自己的需求改动。
学习完本文有没有收获到一点什么呢?学无止境,学习如逆水行舟,不进则退,本文除了以上内容,还准备了许多Android进阶练习的相关资料,为努力奋斗的你无偿献上,希望能帮到你!
扫码领取!Android开发必备进阶资料
![ ](https://img-blog.csdnimg.cn/0742a6ef25ea46daa467aab80483f3fa.png#pic_centerCopyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。