Android仿天天动态上拉播放界面控件

简介:

转自:http://blog.csdn.net/huweigoodboy/article/details/42156209

看到天天动听的播放界面,可以从底部划出来,被其效果惊艳到,于是想自己动手模仿。

                               



                               

效果:1,在Content未展开的状态(隐藏):

                  1>点击Handler控件,弹出Content。

          2>拖动Handler,Content会从底部逐渐出来。

           2,在Content展开的状态:

拖动Content,content位置随着手指的滑动而产生位置变化。


最开始想到的是用SlidingDraw,因为有几分相似,SlidingDraw有一个handle,拖着handle让content跟着移动。

区别在于在我们要设计的控件handle是不动的,监听到handle的touch事件,只能让content去移动。

要自定义一个控件,首先一定要清楚一下几个方法,onMeasure(),用于测量view的大小,onLayout()用于放置view的位置,onDraw()用于绘制控件,onDispatchDraw()一般在这里绘制子控件。这里我们把handle放在最底部,content位于handle下面,也就是屏幕外面。


实现思路:

1,Content关闭状态,touch事件的监听应该在Handler上,也就是说父控件不能去拦截,在onInterceptTouchEvent()中返回false,handler控件去消费onTouch事件。这里用了一个手势辅助类GestureDetector,其实我们要用到只是滑动手势和点击,所以这里不用这个辅助类也可以自己实现。

2,Content展开状态,touch事件的监听应该放在ParentView上,也就是说监听事件是全屏的。(一开始我犯了一个错误,将监听事件放到content上,也就是content去监听滑动,然后移动自己,这样会由于移动content自身后,监听位置更新不及时而发生抖动现象)。

3,当拖动content到屏幕中间而放手,应该去调整content的位置,播放从当前位置到展开位置或者关闭位置的动画。



由于代码比较简单,关于自定义控件的事件监听以及重要的几个方法这里不多啰嗦了。 

SlidingPanel.java

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package com.example.huwei.slidingpaneldemo.ui;  
  2.   
  3.   
  4. import android.content.Context;  
  5. import android.graphics.Canvas;  
  6. import android.graphics.Rect;  
  7. import android.os.Handler;  
  8. import android.util.AttributeSet;  
  9. import android.util.Log;  
  10. import android.view.GestureDetector;  
  11. import android.view.Gravity;  
  12. import android.view.MotionEvent;  
  13. import android.view.View;  
  14. import android.view.animation.AccelerateInterpolator;  
  15. import android.view.animation.Animation;  
  16. import android.view.animation.TranslateAnimation;  
  17. import android.widget.LinearLayout;  
  18. import android.widget.Scroller;  
  19.   
  20.   
  21.   
  22.   
  23. import com.example.huwei.slidingpaneldemo.R;  
  24.   
  25.   
  26.   
  27.   
  28.   
  29.   
  30.   
  31.   
  32. /** 
  33.  * Created by huwei on 14-12-19. 
  34.  */  
  35. public class SlidingPanel extends LinearLayout implements GestureDetector.OnGestureListener {  
  36.     private static final String TAG = "SlidingPanel";  
  37.     private static final String GTAG = "GTAG";    //手势TAG  
  38.   
  39.   
  40.     private View mHandle;  
  41.     private View mContent;  
  42.   
  43.   
  44.     private int mContentRangeTop;   //content在父布局的移动范围  
  45.     private int mContentRangeBottom;  
  46.   
  47.   
  48.     private int mDownY;     //ACTION_DOWN时y的坐标  
  49.     private boolean mExpanded=false;  //是否展开  
  50.   
  51.   
  52.   
  53.   
  54.     private static final int ANIMATION_DURATION = 1000;   //  从底部到上面需要1s  
  55.   
  56.   
  57.     private GestureDetector mGestureDetector;   //检测手势辅助类  
  58.   
  59.   
  60.   
  61.   
  62.   
  63.   
  64.     public SlidingPanel(Context context) {  
  65.         this(context, null);  
  66.     }  
  67.   
  68.   
  69.     public SlidingPanel(Context context, AttributeSet attrs) {  
  70.         this(context, attrs, 0);  
  71.     }  
  72.   
  73.   
  74.     public SlidingPanel(Context context, AttributeSet attrs, int defStyleAttr) {  
  75.         super(context, attrs, defStyleAttr);  
  76.   
  77.   
  78.         mGestureDetector = new GestureDetector(context, this);  
  79.         setOrientation(LinearLayout.VERTICAL);  
  80.     }  
  81.   
  82.   
  83.   
  84.   
  85.     @Override  
  86.     protected void onFinishInflate() {  
  87.         mHandle = findViewById(R.id.handle);  
  88.   
  89.   
  90.         if (mHandle == null) {  
  91.             throw new IllegalArgumentException("The handle attribute is required and must refer "  
  92.                     + "to a valid child.");  
  93.         }  
  94.   
  95.   
  96.         mContent =  findViewById(R.id.content);  
  97.   
  98.   
  99.         if (mContent == null) {  
  100.             throw new IllegalArgumentException("The content attribute is required and must refer "  
  101.                     + "to a valid child.");  
  102.         }  
  103.   
  104.   
  105.         Log.i(TAG, "***********handle:" + mHandle + "************content:" + mContent + "**********");  
  106.           
  107.         mHandle.setClickable(true);  
  108.         mHandle.setOnTouchListener(touchListener);  
  109.     }  
  110.   
  111.   
  112.   
  113.   
  114.     @Override  
  115.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  116.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  117.         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);  
  118.   
  119.   
  120.         int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);  
  121.   
  122.   
  123.         View handle = mHandle;  
  124.   
  125.   
  126.         measureChild(handle, widthMeasureSpec, heightMeasureSpec);  
  127.   
  128.   
  129.         int height = (int) heightSpecSize;  
  130.         measureChild(mContent, MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));  
  131.   
  132.   
  133.         setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);  
  134.     }  
  135.   
  136.   
  137.     @Override  
  138.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  139.         super.onLayout(changed, l, t, r, b);  
  140.   
  141.   
  142.   
  143.   
  144.         //获取自身的宽高  
  145.         final int width = r - l;  
  146.         final int height = b - t;  
  147.   
  148.   
  149.         mContentRangeTop=0;  
  150.         mContentRangeBottom = b - t;  
  151.   
  152.   
  153.         final View handle = mHandle;  
  154.         int childHeight = handle.getMeasuredHeight();  
  155.         int childWidth = handle.getMeasuredWidth();  
  156.   
  157.   
  158.         handle.layout(0, height - childHeight, childWidth, height);  
  159.   
  160.   
  161.         final View content = mContent;  
  162.         if (!mExpanded) {  
  163.             mContent.layout(0, mContentRangeBottom, content.getMeasuredWidth(), mContentRangeBottom + content.getMeasuredHeight());  
  164.         } else {  
  165.             mContent.layout(0, mContentRangeTop, content.getMeasuredWidth(), mContentRangeTop + content.getMeasuredHeight());  
  166.         }  
  167.     }  
  168.   
  169.   
  170.     @Override  
  171.     protected void dispatchDraw(Canvas canvas) {  
  172.         super.dispatchDraw(canvas);  
  173.   
  174.   
  175.         long drawingTime = getDrawingTime();  
  176.   
  177.   
  178.         final View handle = mHandle;  
  179.         drawChild(canvas, handle, drawingTime);  
  180.   
  181.   
  182.         final View content = mContent;  
  183.         drawChild(canvas, content, drawingTime);  
  184.     }  
  185.   
  186.   
  187.   
  188.   
  189.     @Override  
  190.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  191.         return mExpanded;  
  192.     }  
  193.   
  194.   
  195.     @Override  
  196.     public boolean onTouchEvent(MotionEvent event) {  
  197.         switch (event.getAction()){  
  198.             case MotionEvent.ACTION_DOWN:  
  199.                 mDownY= (int)event.getY();  
  200.                 break;  
  201.             case MotionEvent.ACTION_MOVE:  
  202.                 int nowY=(int)event.getY();  
  203.                   
  204.                 moveContent(nowY-mDownY);  
  205.                 break;  
  206.             case MotionEvent.ACTION_UP:  
  207.                 adjustContentView();  
  208.                 break;  
  209.         }  
  210.         return  mExpanded;  
  211.     }  
  212.    
  213.   
  214.   
  215.     /** 
  216.      * 停止滑动 
  217.      * 
  218.      * @param 
  219.      */  
  220.     private void stopTracking() {  
  221.         //判断content是展开还是收缩  
  222.         updateExpanded();  
  223.     }  
  224.   
  225.   
  226.     /** 
  227.      * 更新mExpanded状态 
  228.      */  
  229.     private void updateExpanded(){  
  230.         if (mContent.getTop() <= mContentRangeTop) {  
  231.             mExpanded = true;  
  232.         } else {  
  233.             mExpanded = false;  
  234.         }  
  235.     }  
  236.   
  237.   
  238.     /** 
  239.      * move content到指定位置 
  240.      * 
  241.      * @param position 
  242.      */  
  243.     private void moveContent(int position) {  
  244.   
  245.   
  246.   
  247.   
  248.         Log.i(GTAG, "*********move Content:position" + position + "**********");  
  249.         //不能移出上边界  
  250.         if (position < mContentRangeTop) {  
  251.             position = mContentRangeTop;  
  252.         } else if (position > mContentRangeBottom) {  
  253.             position = mContentRangeBottom;  
  254.         }  
  255.   
  256.   
  257.         final View content=mContent;  
  258.         final int top = (int) mContent.getY();  
  259.         final int deltaY = position - top;  
  260.   
  261.   
  262.   
  263.   
  264.   
  265.   
  266.         content.layout(0,position,content.getWidth(),position+content.getHeight());  
  267.     }  
  268.   
  269.   
  270.     //移动Content  
  271.     private void doAnimation(int nowY, final int targetY) {  
  272.         AccelerateInterpolator accelerateInterpolator = new AccelerateInterpolator();  
  273.         //BounceInterpolator bounceInterpolator=new BounceInterpolator();  
  274.         TranslateAnimation animation = new TranslateAnimation(000, targetY - nowY);  
  275.         animation.setDuration(ANIMATION_DURATION * Math.abs(targetY - nowY) / mContentRangeBottom);  
  276.         animation.setInterpolator(accelerateInterpolator);  
  277.         animation.setAnimationListener(new Animation.AnimationListener() {  
  278.             @Override  
  279.             public void onAnimationStart(Animation animation) {  
  280.   
  281.   
  282.   
  283.   
  284.             }  
  285.   
  286.   
  287.             @Override  
  288.             public void onAnimationEnd(Animation animation) {  
  289.                 //Log.i(TAG,"onAnimationEnd");  
  290.                 mContent.clearAnimation();  
  291.                 mContent.layout(0, targetY, mContent.getMeasuredWidth(), targetY + mContent.getMeasuredHeight());  
  292.   
  293.   
  294.                 stopTracking();  
  295.             }  
  296.   
  297.   
  298.             @Override  
  299.             public void onAnimationRepeat(Animation animation) {  
  300.   
  301.   
  302.             }  
  303.         });  
  304.   
  305.   
  306.         mContent.startAnimation(animation);  
  307.   
  308.   
  309.     }  
  310.   
  311.   
  312.     /** 
  313.      * 点击时打开Content* 
  314.      */  
  315.     private void open() {  
  316.         doAnimation(mContentRangeBottom, 0);  
  317.         stopTracking();  
  318.     }  
  319.   
  320.   
  321.   
  322.   
  323.     /** 
  324.      * ACTION_UP时 contentView不是停靠在屏幕边缘(在屏幕中间)时,调整contentView的位置* 
  325.      */  
  326.     private void adjustContentView(){  
  327.         final int top = mContent.getTop();  
  328.   
  329.   
  330.         //切割父容器,分成3等份  
  331.         final int perRange=(mContentRangeBottom-mContentRangeTop)/3;  
  332.         if(mExpanded){  
  333.             //小于1/3  
  334.             if(top<perRange+mContentRangeTop){  
  335.                 doAnimation(top,0);  
  336.             }else{  
  337.                 doAnimation(top, mContentRangeBottom);  
  338.             }  
  339.         }else{  
  340.             //小于2/3  
  341.             if(top<mContentRangeTop+perRange*2){  
  342.                 doAnimation(top,0);  
  343.             }else{  
  344.                 doAnimation(top,mContentRangeBottom);  
  345.             }  
  346.         }  
  347.           
  348.     }  
  349.   
  350.   
  351.     /** 
  352.      *按下时触发*  
  353.      * @param e 
  354.      * @return 
  355.      */  
  356.     @Override  
  357.     public boolean onDown(MotionEvent e) {  
  358.         Log.i(GTAG, "onDown:"+e.getY());  
  359.         return false;  
  360.     }  
  361.   
  362.   
  363.     @Override  
  364.     public void onShowPress(MotionEvent e) {  
  365.         Log.i(GTAG, "onShowPress");  
  366.     }  
  367.   
  368.   
  369.     /** 
  370.      * 轻轻点击*  
  371.      * @param e 
  372.      * @return 
  373.      */  
  374.     @Override  
  375.     public boolean onSingleTapUp(MotionEvent e) {  
  376.         Log.i(GTAG, "onSingleTapUp");  
  377.         if (!mExpanded) {  
  378.             open();  
  379.         }  
  380.         return false;  
  381.     }  
  382.   
  383.   
  384.     /** 
  385.      * 滚动时出发*  
  386.      * @param e1 
  387.      * @param e2 
  388.      * @param distanceX 
  389.      * @param distanceY 
  390.      * @return 
  391.      */  
  392.     @Override  
  393.     public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {  
  394.   
  395.   
  396.         int touchY = (int) e2.getY();  
  397.         Log.i(GTAG, "onScroll:"+mContent.getY());  
  398.         moveContent(touchY - mDownY + (mExpanded ? mContentRangeTop : mContentRangeBottom));  
  399.         return false;  
  400.     }  
  401.   
  402.   
  403.     /** 
  404.      * 长按*  
  405.      * @param e 
  406.      */  
  407.     @Override  
  408.     public void onLongPress(MotionEvent e) {  
  409.         Log.i(GTAG, "onLongPress");  
  410.     }  
  411.   
  412.   
  413.     /** 
  414.      * 瞬时滑动* 
  415.      * @param e1 
  416.      * @param e2 
  417.      * @param velocityX 
  418.      * @param velocityY 
  419.      * @return 
  420.      */  
  421.     @Override  
  422.     public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {  
  423.         Log.i(GTAG, "onFling");  
  424.         return false;  
  425.     }  
  426.   
  427.   
  428.     OnTouchListener touchListener = new OnTouchListener() {  
  429.         @Override  
  430.         public boolean onTouch(View v, MotionEvent event) {  
  431.             final View handle = mHandle;  
  432.             if (event.getAction() == MotionEvent.ACTION_DOWN) {  
  433.                 final int top = handle.getTop();  
  434.                 mDownY = (int) event.getY();  
  435.   
  436.   
  437.                 Log.i(GTAG,"mDownY:"+mDownY);  
  438.             } else if (event.getAction() == MotionEvent.ACTION_UP) {  
  439.                  adjustContentView();  
  440.             }  
  441.             Log.i(GTAG, "onTouch:" + event.getAction() + " y:" + event.getY());  
  442.             return mGestureDetector.onTouchEvent(event);  
  443.         }  
  444.   
  445.   
  446.     };  
  447. }  

xml

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <com.example.huwei.slidingpaneldemo.ui.SlidingPanel xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="match_parent">  
  4.   
  5.   
  6.     <Button  
  7.         android:layout_width="wrap_content"  
  8.         android:layout_height="wrap_content" />  
  9.     <Button  
  10.         android:layout_width="wrap_content"  
  11.         android:layout_height="wrap_content" />  
  12.   
  13.     <LinearLayout  
  14.         android:id="@+id/handle"  
  15.         android:orientation="horizontal"  
  16.         android:layout_width="fill_parent"  
  17.         android:layout_height="68dp"  
  18.         android:background="@drawable/handle_seletor">  
  19.   
  20.         <Button  
  21.             android:layout_width="wrap_content"  
  22.             android:layout_height="wrap_content" />  
  23.         </LinearLayout>  
  24.   
  25.     <LinearLayout  
  26.         android:orientation="vertical"  
  27.         android:id="@+id/content"  
  28.         android:background="#78ADFF2F"  
  29.         android:layout_width="match_parent"  
  30.         android:layout_height="match_parent" >  
  31.         <Button   
  32.             android:layout_width="wrap_content"  
  33.             android:layout_height="wrap_content"/>  
  34.     </LinearLayout>  
  35. </com.example.huwei.slidingpaneldemo.ui.SlidingPanel>  

百度网盘地址

csdn下载

相关文章
|
5月前
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
|
5月前
|
XML Java Android开发
Android自定义view之网易云推荐歌单界面
本文详细介绍了如何通过自定义View实现网易云音乐推荐歌单界面的效果。首先,作者自定义了一个圆角图片控件`MellowImageView`,用于绘制圆角矩形图片。接着,通过将布局放入`HorizontalScrollView`中,实现了左右滑动功能,并使用`ViewFlipper`添加图片切换动画效果。文章提供了完整的代码示例,包括XML布局、动画文件和Java代码,最终展示了实现效果。此教程适合想了解自定义View和动画效果的开发者。
223 65
Android自定义view之网易云推荐歌单界面
|
5月前
|
XML 搜索推荐 Android开发
Android改变进度条控件progressbar的样式(根据源码修改)
本文介绍了如何基于Android源码自定义ProgressBar样式。首先分析了系统源码中ProgressBar样式的定义,发现其依赖一张旋转图片实现动画效果。接着分两步指导开发者实现自定义:1) 模仿源码创建一个旋转动画XML文件(放置在drawable文件夹),修改图片为自定义样式;2) 在UI控件中通过`indeterminateDrawable`属性应用该动画。最终实现简单且个性化的ProgressBar效果,附带效果图展示。
278 2
|
5月前
|
Android开发
Android控件样式的抽取(小提及快捷方式)
在Android开发中,若多个控件样式重复,可抽取公共部分以简化代码。例如对EditText提取样式,通过编辑`styles.xml`实现复用。为提高效率,Android Studio提供自动提取Style功能:右键点击控件样式选项,选择“Style...”,勾选需要提取的属性后确认,即可快速生成样式代码,显著提升开发便利性。
145 2
|
5月前
|
Android开发 开发者
Android企业级实战-界面篇-3
本文是《Android企业级实战-界面篇》系列的第三篇,主要介绍分割线和条形跳转框的实现方法,二者常用于设置和个人中心界面。文章通过具体代码示例展示了如何实现这两种UI组件,并提供了效果图。实现前需准备`dimens.xml`、`ids.xml`、`colors.xml`等文件,部分资源可参考系列第一、二篇文章。代码中详细说明了布局文件的配置,如分割线的样式定义和条形跳转框的组件组合,帮助开发者快速上手并应用于实际项目中。
|
5月前
|
XML Android开发 数据格式
Android企业级实战-界面篇-2
本文为《Android企业级实战-界面篇》系列第二篇,主要介绍三个UI模块的实现:用户资料模块、关注与粉丝统计模块以及喜欢和收藏功能模块。通过详细的XML代码展示布局设计,包括dimens、ids、colors配置文件的使用,帮助开发者快速构建美观且功能齐全的界面。文章结合实际效果图,便于理解和应用。建议配合第一篇文章内容学习,以获取完整工具类支持。
|
5月前
|
算法 Java Android开发
Android企业级实战-界面篇-1
本文详细介绍了Android企业级开发中界面实现的过程,涵盖效果展示、实现前准备及代码实现。作者通过自身经历分享了Android开发经验,并提供了`dimens.xml`、`ids.xml`、`colors.xml`和`strings.xml`等配置文件内容,帮助开发者快速构建规范化的UI布局。文章以一个具体的用户消息界面为例,展示了如何使用线性布局(LinearLayout)和相对布局(RelativeLayout)实现功能模块排列,并附带注意事项及使用方法,适合初学者和进阶开发者参考学习。
|
12月前
|
XML 数据可视化 Android开发
Android应用界面
Android应用界面中的布局和控件使用,包括相对布局、线性布局、表格布局、帧布局、扁平化布局等,以及AdapterView及其子类如ListView的使用方法和Adapter接口的应用。
207 0
Android应用界面
|
XML Android开发 UED
💥Android UI设计新风尚!掌握Material Design精髓,让你的界面颜值爆表!🎨
随着移动应用市场的蓬勃发展,用户对界面设计的要求日益提高。为此,掌握由Google推出的Material Design设计语言成为提升应用颜值和用户体验的关键。本文将带你深入了解Material Design的核心原则,如真实感、统一性和创新性,并通过丰富的组件库及示例代码,助你轻松打造美观且一致的应用界面。无论是色彩搭配还是动画效果,Material Design都能为你的Android应用增添无限魅力。
288 1
|
XML 编解码 Android开发
安卓开发中的自定义视图控件
【9月更文挑战第14天】在安卓开发中,自定义视图控件是一种高级技巧,它可以让开发者根据项目需求创建出独特的用户界面元素。本文将通过一个简单示例,引导你了解如何在安卓项目中实现自定义视图控件,包括创建自定义控件类、处理绘制逻辑以及响应用户交互。无论你是初学者还是有经验的开发者,这篇文章都会为你提供有价值的见解和技巧。
155 3

热门文章

最新文章