1.
我们用到了自定义viewgroup.
实现左中右三个页面的左右滑动,类似于viewpager的滑动
//自定义viewgroup
public class DragableLuncher extends ViewGroup {
//按钮背景颜色
int choseColor , defaultColor;
//底部按钮数组
ImageButton[] bottomBar;
//滚动对象
private Scroller mScroller;
//触摸功能类 触摸速度
private VelocityTracker mVelocityTracker;
//滚动起始坐标
private int mScrollX = 0;
//显示第几view
private int mCurrentScreen = 0;
//滚动结束X坐标
private float mLastMotionX;
private static final int SNAP_VELOCITY = 1000;
private final static int TOUCH_STATE_REST = 0;
private final static int TOUCH_STATE_SCROLLING = 1;
private int mTouchState = TOUCH_STATE_REST;
//用户最小滑动距离
private int mTouchSlop = 0;
public DragableLuncher(Context context) {
super(context);
mScroller = new Scroller(context);
//初始化最小滑动距离
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
this.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT , LayoutParams.FILL_PARENT));
}
public DragableLuncher(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
//初始化最小滑动距离
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
this.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT , LayoutParams.FILL_PARENT));
//xml中 默认显示第几个view在屏幕中
TypedArray a = context.obtainStyledAttributes(attrs , R.styleable.DragableLuncher);
mCurrentScreen = a.getInteger(R.styleable.DragableLuncher_default_screen , 0);
a.recycle();
}
//touch拦截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)){
return true;
}
final float x = ev.getX();
switch (action){
case MotionEvent.ACTION_DOWN:
mLastMotionX = x;
mTouchState = mScroller.isFinished()?TOUCH_STATE_REST:TOUCH_STATE_SCROLLING;
break;
case MotionEvent.ACTION_MOVE:
final int xDiff = (int) Math.abs(x-mLastMotionX);
boolean xMoved = xDiff > mTouchSlop;
if (xMoved){
mTouchState = TOUCH_STATE_SCROLLING;
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mTouchState = TOUCH_STATE_REST;
break;
}
return super.onInterceptTouchEvent(ev);
}
//触摸滑动打开
public boolean isOpen = true;
public boolean isOpenTouchAnima(boolean b){
isOpen = b;
return isOpen;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isOpen){
if (mVelocityTracker == null){
mVelocityTracker = VelocityTracker.obtain();
}
//将触摸事件添加到速度探测器中
mVelocityTracker.addMovement(event);
final float x = event.getX();
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (!mScroller.isFinished()){
mScroller.abortAnimation();
}
mLastMotionX = x;
break;
case MotionEvent.ACTION_MOVE:
final int deltaX = (int) (mLastMotionX - x);
mLastMotionX = x;
if (deltaX < 0){
//如果mscrollx小于等于0.这不要向右滑动,界面往左了,因为已经在最左边了.
if (mScrollX > 0){
scrollBy(Math.max(-mScrollX , deltaX) , 0);
}
}
else if (deltaX > 0){
//向左滑,界面往右,取得最大滑动距离
final int availableToScroll = getChildAt(getChildCount() - 1).getRight() - mScrollX - getWidth();
if (availableToScroll > 0){
scrollBy(Math.min(availableToScroll , deltaX) , 0);
}
}
break;
case MotionEvent.ACTION_UP:
final VelocityTracker velocityTracker = mVelocityTracker;
//你想要的单位速度。1的值提供每毫秒像素,1000提供每秒像素等
velocityTracker.computeCurrentVelocity(1000);
//得到x方向上的速度
int velocityX = (int) velocityTracker.getXVelocity();
//左右滑动有速度的话界面要很快的滑动
if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0){
//滑到左边界面
snapToScreen(mCurrentScreen - 1);
}
else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1){
//滑到右边界面
snapToScreen(mCurrentScreen + 1);
}
else {
snapToDesitination();
}
if (mVelocityTracker != null){
mVelocityTracker.recycle();
mVelocityTracker = null;
}
mTouchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_CANCEL:
mTouchState = TOUCH_STATE_REST;
}
mScrollX = this.getScrollX();
}
else {
return false;
}
if (bottomBar != null){
for (int k = 0 ; k < bottomBar.length ; k++){
if (k == mCurrentScreen){
bottomBar[k].setBackgroundColor(choseColor);
} else {
bottomBar[k].setBackgroundColor(defaultColor);
}
}
}
return true;
}
public void setBottomBarBg(ImageButton[] ib , int choseColor , int defaultColor){
this.bottomBar = ib;
this.choseColor = choseColor;
this.defaultColor = defaultColor;
}
public void snapToDesitination(){
final int screenWidth = getWidth();
//mSrcrolx表示滑动的距离,向右的为正,向左滑为负
final int whichScreen = (mScrollX + (screenWidth/2))/screenWidth;
snapToScreen(whichScreen);
}
//带动画跳转界面
public void snapToScreen(int whichScreen) {
mCurrentScreen = whichScreen;
final int newX = whichScreen*getWidth();
//当手离开屏幕的一瞬间,view要滑到上一页或下一页,但并不是从这页的左边开始滑动,还要计算在move
//过程中已经滑动的距离,从这个位置开始滑动到上一页或下一页
final int deltaX = newX - mScrollX;
mScroller.startScroll(mScrollX , 0 , deltaX , 0 , Math.abs(deltaX)*2 );
invalidate();
}
//不带动画跳转界面
public void setToScreen(int whichScreen){
mCurrentScreen = whichScreen;
final int newX = whichScreen*getWidth();
mScroller.startScroll(newX , 0 , 0 , 0 , 10);
//不带动画的显示界面
invalidate();
}
//当前是第几屏幕view
public int getCurrentScreen(){
return mCurrentScreen;
}
//主界面布局
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
final int count = getChildCount();
for (int i = 0 ; i< count ; i++){
final View child = getChildAt(i);
if (child.getVisibility() != View.GONE){
final int childWidth = child.getMeasuredWidth();
child.layout(childLeft , 0 , childLeft + childWidth , child.getMeasuredHeight());
childLeft += childWidth;
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY){
throw new IllegalStateException("error mode");
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY){
throw new IllegalStateException("error mode");
}
final int count = getChildCount();
for (int i = 0 ; i < count ; i++){
getChildAt(i).measure(widthMeasureSpec , heightMeasureSpec);
}
//滚动到指定的view
scrollTo(mCurrentScreen*width , 0);
}
//1.真正让ViewGroup滑动的是scrollTo,scrollBy。
// computeScroll的作用是计算ViewGroup如何滑动。而computeScroll是通过draw来调用的
//2.computeScroll和Scroller要是飞得拉关系的话,那就是computeScroll可以参考Scroller计算结果来影响scrollTo,scrollBy,从而使得滑动发生改变。
// 也就是Scroller不会调用computeScroll,反而是computeScroll调用Scroller。
//3.如上所说computeScroll调用Scroller,只要computeScroll调用连续,Scroller也会连续,
// 实质上computeScroll的连续性又invalidate方法控制,scrollTo,scrollBy都会调用invalidate,
// 而invalidate回去触发draw,从而computeScroll被连续调用,
// 综上,Scroller也会被连续调用,除非invalidate停止调用。
//4.computeScroll参考Scroller影响scrollTo,scrollBy,实质上,为了不重复影响scrollTo,scrollBy,
// 那么Scroller必须终止计算currX,currY。要知道计算有没有终止,
// 需要通过mScroller.computeScrollOffset()
//5.https://www.linuxidc.com/Linux/2016-01/127276.htm [[理解scroll]]
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()){
mScrollX = mScroller.getCurrX();
scrollTo(mScrollX , 0);
postInvalidate();
}
}
}
2.
也是一个自定义viewgroup 和上面那个的作用类似.
public class BigDragableLuncher extends ViewGroup {
int choseColor , defaultColor;
//底部按钮数组
ImageButton[] bottombar;
private Scroller mScroller;
//滚动起始x坐标
private int mScrollX = 0;
//显示第几屏幕的view
private int mCurrentScreen = 0;
public int mTouchSlop = 0;
public BigDragableLuncher(Context context) {
super(context);
mScroller = new Scroller(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
this.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.FILL_PARENT));
}
public BigDragableLuncher(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
this.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.FILL_PARENT));
//获取属性文件
TypedArray a = getContext().obtainStyledAttributes(attrs , R.styleable.DragableLuncher);
//获取具体属性
mCurrentScreen = a.getInteger(R.styleable.DragableLuncher_default_screen , 0);
a.recycle();
}
public void setBottombarBg(ImageButton[] lb , int choseColor , int defaultColor){
this.bottombar = lb;
this.choseColor = choseColor;
this.defaultColor = defaultColor;
}
public void snapToDestination(){
final int screenWidth = getWidth();
//滑动超过1/2屏幕,进入下一个view
final int whichScreen = (mScrollX + (screenWidth/2))/screenWidth;
snapToScreen(whichScreen);
}
//屏幕滑动
private void snapToScreen(int whichScreen) {
mCurrentScreen = whichScreen;
final int newX = whichScreen*getWidth();
final int detal = newX -mScrollX;
//主要代码
mScroller.startScroll(mScrollX , 0 , detal , 0 , Math.abs(detal)*2);
invalidate();
}
public void setToscreen(int whichScreen){
mCurrentScreen = whichScreen;
final int newX = whichScreen*getWidth();
//主要代码,无动画显示界面
mScroller.startScroll(newX , 0 , 0 , 0 , 10);
invalidate();
}
public int getCurrentScreen(){
return mCurrentScreen;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
final int count = getChildCount();
for (int i = 0 ; i < count; i++){
final View child = getChildAt(i);
if (child.getVisibility() != View.GONE){
final int childWidth = child.getMeasuredWidth();
child.layout(childLeft , 0 , childLeft + childWidth , child.getMeasuredHeight());
childLeft += childWidth;
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int height = MeasureSpec.getSize(heightMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY){
throw new IllegalStateException("error mode");
}
if (heightMode != MeasureSpec.EXACTLY){
throw new IllegalStateException("error mode");
}
//将宽高信息传递给子元素
final int count = getChildCount();
for (int i = 0 ; i < count ; i++){
getChildAt(i).measure(widthMeasureSpec , heightMeasureSpec);
}
scrollTo(mCurrentScreen*width , 0);
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()){
mScrollX = mScroller.getCurrX();
scrollTo(mScrollX , 0);
postInvalidate();
}
}
}
3.
对于上面那个view,要改动一个地方
//获取属性文件 //在布局中使用自定义属性时发现并不会自动提示声明好的属性, // 一直困惑了很久才发现自定义属性的名称必须和自定义view的类名一样才会有提示 TypedArray a = context.obtainStyledAttributes(attrs , R.styleable.BigDragableLuncher); //获取具体属性 mCurrentScreen = a.getInteger(R.styleable.BigDragableLuncher_default_screen , 0); a.recycle();
主界面结构布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:liuyan="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/musicplayer_bkg"
android:gravity="center"
android:layout_gravity="top"
>
<com.example.liuyan.mp3play.customview.BigDragableLuncher
liuyan:default_screen="1"
android:id="@+id/all_space"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include android:id="@+id/music_list" layout="@layout/music_list"/>
<RelativeLayout
android:id="@+id/linearlayout1"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<RelativeLayout
android:id="@+id/linearlayout2"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="100dip">
<!--音乐名称-->
<TextView
android:id="@+id/music_name"
android:text="无歌曲播放"
android:textSize="19sp"
android:textStyle="bold"
android:textColor="#ddffffff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="11dip"
android:layout_marginLeft="20dip"
android:singleLine="true"
/>
<!--音乐专辑-->
<TextView
android:id="@+id/music_album"
android:textSize="36sp"
android:textColor="#66ceedf9"
android:textStyle="bold"
android:layout_marginTop="28dip"
android:layout_marginLeft="5dip"
android:singleLine="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:layout_width="300dip"
android:layout_height="wrap_content" />
<!--歌手名字-->
<TextView
android:id="@+id/music_artist"
android:textSize="15sp"
android:textColor="#ffffff"
android:layout_marginTop="58dip"
android:layout_marginLeft="17dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!--歌曲序号-->
<TextView
android:id="@+id/music_number"
android:text="0/0"
android:textSize="22sp"
android:textStyle="bold"
android:textColor="#bbf3731e"
android:layout_marginTop="73dip"
android:layout_marginRight="20dip"
android:gravity="center_horizontal"
android:layout_alignParentRight="true"
android:layout_width="60dip"
android:layout_height="wrap_content" />
</RelativeLayout>
<!--中间可滚动部分界面-->
<RelativeLayout
android:id="@+id/relativelayout1"
android:layout_width="fill_parent"
android:layout_height="260dip"
android:layout_below="@+id/linearlayout2"
>
<com.example.liuyan.mp3play.customview.DragableLuncher
android:id="@+id/space"
liuyan:default_screen="1"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<!--左边显示动画界面-->
<include android:id="@+id/left" layout="@layout/left_mediaview"/>
<!--中间显示专辑界面-->
<include android:id="@+id/center" layout="@layout/center_special"/>
<!--右边显示歌词-->
<include android:id="@+id/right" layout="@layout/right_lrc"/>
</com.example.liuyan.mp3play.customview.DragableLuncher>
</RelativeLayout>
<RelativeLayout
android:id="@+id/relativelayout2"
android:layout_width="fill_parent"
android:layout_height="72dip"
android:layout_below="@+id/relativelayout1"
>
<!--歌词显示进度-->
<LinearLayout
android:id="@+id/linearlayout3"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!--当前播放时间-->
<TextView
android:id="@+id/time_tv1"
android:text=" 00:00 "
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#bb7af9fe"
android:layout_weight="1"
android:gravity="left"
android:layout_width="35dip"
android:layout_height="18dip" />
<!--显示播放进度-->
<SeekBar
android:id="@+id/player_seekbar"
android:progressDrawable="@drawable/seekbar_style"
android:background="@drawable/play_progress_background"
android:layout_width="220dip"
android:layout_height="wrap_content"
android:thumb="@drawable/thumb"
android:progress="0"
android:max="0"
/>
<!--显示歌曲总时间-->
<TextView
android:id="@+id/time_tv2"
android:text=" 00:00 "
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#bb7af9fe"
android:layout_weight="1"
android:gravity="right"
android:layout_width="35dip"
android:layout_height="18dip" />
</LinearLayout>
<!--控制音乐播放按钮-->
<LinearLayout
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!--前一首-->
<ImageButton
android:id="@+id/ib1"
android:src="@drawable/left_button"
android:background="#00000000"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!--暂停播放按钮-->
<ImageButton
android:id="@+id/ib2"
android:background="@drawable/play_button"
android:layout_marginLeft="40dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!--下一首-->
<ImageButton
android:id="@+id/ib3"
android:src="@drawable/right_button"
android:background="#00000000"
android:layout_marginLeft="40dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</RelativeLayout>
</RelativeLayout>
<!--右边界面,显示专辑列表-->
<include android:id="@+id/gride_special" layout="@layout/gridspecial"/>
</com.example.liuyan.mp3play.customview.BigDragableLuncher>
<!--底部按钮界面,用于切换歌曲列表、正在播放、专辑列表-->
<LinearLayout
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_width="fill_parent"
android:layout_height="40dip">
<!--歌曲列表按钮-->
<Button
android:id="@+id/Button1"
android:text="歌曲列表"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#fafa2d"
android:background="#00000000"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
<!--正在播放按钮-->
<Button
android:id="@+id/Button2"
android:text="正在播放"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#fafa2d"
android:background="#00000000"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
<!--专辑列表按钮-->
<Button
android:id="@+id/Button3"
android:text="专辑列表"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#fafa2d"
android:background="#00000000"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_width="fill_parent"
android:layout_height="40dip">
<ImageButton
android:id="@+id/imageButton1"
android:src="@drawable/big_button_style"
android:background="#00000000"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageButton
android:id="@+id/imageButton2"
android:src="@drawable/big_button_style"
android:background="#00000000"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageButton
android:id="@+id/imageButton3"
android:src="@drawable/big_button_style"
android:background="#00000000"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</RelativeLayout>