仿天天动态上拉播放界面控件
来源:互联网 发布:免费漫画阅读软件 编辑:程序博客网 时间:2024/09/21 09:03
看到天天动听的播放界面,可以从底部划出来,被其效果惊艳到,于是想自己动手模仿。
效果: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
package com.example.huwei.slidingpaneldemo.ui;import android.content.Context;import android.graphics.Canvas;import android.graphics.Rect;import android.os.Handler;import android.util.AttributeSet;import android.util.Log;import android.view.GestureDetector;import android.view.Gravity;import android.view.MotionEvent;import android.view.View;import android.view.animation.AccelerateInterpolator;import android.view.animation.Animation;import android.view.animation.TranslateAnimation;import android.widget.LinearLayout;import android.widget.Scroller;import com.example.huwei.slidingpaneldemo.R;/** * Created by huwei on 14-12-19. */public class SlidingPanel extends LinearLayout implements GestureDetector.OnGestureListener { private static final String TAG = "SlidingPanel"; private static final String GTAG = "GTAG"; //手势TAG private View mHandle; private View mContent; private int mContentRangeTop; //content在父布局的移动范围 private int mContentRangeBottom; private int mDownY; //ACTION_DOWN时y的坐标 private boolean mExpanded=false; //是否展开 private static final int ANIMATION_DURATION = 1000; // 从底部到上面需要1s private GestureDetector mGestureDetector; //检测手势辅助类 public SlidingPanel(Context context) { this(context, null); } public SlidingPanel(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SlidingPanel(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mGestureDetector = new GestureDetector(context, this); setOrientation(LinearLayout.VERTICAL); } @Override protected void onFinishInflate() { mHandle = findViewById(R.id.handle); if (mHandle == null) { throw new IllegalArgumentException("The handle attribute is required and must refer " + "to a valid child."); } mContent = findViewById(R.id.content); if (mContent == null) { throw new IllegalArgumentException("The content attribute is required and must refer " + "to a valid child."); } Log.i(TAG, "***********handle:" + mHandle + "************content:" + mContent + "**********"); mHandle.setClickable(true); mHandle.setOnTouchListener(touchListener); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); View handle = mHandle; measureChild(handle, widthMeasureSpec, heightMeasureSpec); int height = (int) heightSpecSize; measureChild(mContent, MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); //获取自身的宽高 final int width = r - l; final int height = b - t; mContentRangeTop=0; mContentRangeBottom = b - t; final View handle = mHandle; int childHeight = handle.getMeasuredHeight(); int childWidth = handle.getMeasuredWidth(); handle.layout(0, height - childHeight, childWidth, height); final View content = mContent; if (!mExpanded) { mContent.layout(0, mContentRangeBottom, content.getMeasuredWidth(), mContentRangeBottom + content.getMeasuredHeight()); } else { mContent.layout(0, mContentRangeTop, content.getMeasuredWidth(), mContentRangeTop + content.getMeasuredHeight()); } } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); long drawingTime = getDrawingTime(); final View handle = mHandle; drawChild(canvas, handle, drawingTime); final View content = mContent; drawChild(canvas, content, drawingTime); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return mExpanded; } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: mDownY= (int)event.getY(); break; case MotionEvent.ACTION_MOVE: int nowY=(int)event.getY(); moveContent(nowY-mDownY); break; case MotionEvent.ACTION_UP: adjustContentView(); break; } return mExpanded; } /** * 停止滑动 * * @param */ private void stopTracking() { //判断content是展开还是收缩 updateExpanded(); } /** * 更新mExpanded状态 */ private void updateExpanded(){ if (mContent.getTop() <= mContentRangeTop) { mExpanded = true; } else { mExpanded = false; } } /** * move content到指定位置 * * @param position */ private void moveContent(int position) { Log.i(GTAG, "*********move Content:position" + position + "**********"); //不能移出上边界 if (position < mContentRangeTop) { position = mContentRangeTop; } else if (position > mContentRangeBottom) { position = mContentRangeBottom; } final View content=mContent; final int top = (int) mContent.getY(); final int deltaY = position - top; content.layout(0,position,content.getWidth(),position+content.getHeight()); } //移动Content private void doAnimation(int nowY, final int targetY) { AccelerateInterpolator accelerateInterpolator = new AccelerateInterpolator(); //BounceInterpolator bounceInterpolator=new BounceInterpolator(); TranslateAnimation animation = new TranslateAnimation(0, 0, 0, targetY - nowY); animation.setDuration(ANIMATION_DURATION * Math.abs(targetY - nowY) / mContentRangeBottom); animation.setInterpolator(accelerateInterpolator); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { //Log.i(TAG,"onAnimationEnd"); mContent.clearAnimation(); mContent.layout(0, targetY, mContent.getMeasuredWidth(), targetY + mContent.getMeasuredHeight()); stopTracking(); } @Override public void onAnimationRepeat(Animation animation) { } }); mContent.startAnimation(animation); } /** * 点击时打开Content* */ private void open() { doAnimation(mContentRangeBottom, 0); stopTracking(); } /** * ACTION_UP时 contentView不是停靠在屏幕边缘(在屏幕中间)时,调整contentView的位置* */ private void adjustContentView(){ final int top = mContent.getTop(); //切割父容器,分成3等份 final int perRange=(mContentRangeBottom-mContentRangeTop)/3; if(mExpanded){ //小于1/3 if(top<perRange+mContentRangeTop){ doAnimation(top,0); }else{ doAnimation(top, mContentRangeBottom); } }else{ //小于2/3 if(top<mContentRangeTop+perRange*2){ doAnimation(top,0); }else{ doAnimation(top,mContentRangeBottom); } } } /** *按下时触发* * @param e * @return */ @Override public boolean onDown(MotionEvent e) { Log.i(GTAG, "onDown:"+e.getY()); return false; } @Override public void onShowPress(MotionEvent e) { Log.i(GTAG, "onShowPress"); } /** * 轻轻点击* * @param e * @return */ @Override public boolean onSingleTapUp(MotionEvent e) { Log.i(GTAG, "onSingleTapUp"); if (!mExpanded) { open(); } return false; } /** * 滚动时出发* * @param e1 * @param e2 * @param distanceX * @param distanceY * @return */ @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { int touchY = (int) e2.getY(); Log.i(GTAG, "onScroll:"+mContent.getY()); moveContent(touchY - mDownY + (mExpanded ? mContentRangeTop : mContentRangeBottom)); return false; } /** * 长按* * @param e */ @Override public void onLongPress(MotionEvent e) { Log.i(GTAG, "onLongPress"); } /** * 瞬时滑动* * @param e1 * @param e2 * @param velocityX * @param velocityY * @return */ @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { Log.i(GTAG, "onFling"); return false; } OnTouchListener touchListener = new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { final View handle = mHandle; if (event.getAction() == MotionEvent.ACTION_DOWN) { final int top = handle.getTop(); mDownY = (int) event.getY(); Log.i(GTAG,"mDownY:"+mDownY); } else if (event.getAction() == MotionEvent.ACTION_UP) { adjustContentView(); } Log.i(GTAG, "onTouch:" + event.getAction() + " y:" + event.getY()); return mGestureDetector.onTouchEvent(event); } };}
xml
<com.example.huwei.slidingpaneldemo.ui.SlidingPanel xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" /> <LinearLayout android:id="@+id/handle" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="68dp" android:background="@drawable/handle_seletor"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <LinearLayout android:orientation="vertical" android:id="@+id/content" android:background="#78ADFF2F" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout></com.example.huwei.slidingpaneldemo.ui.SlidingPanel>
百度网盘地址
csdn下载
- 仿天天动态上拉播放界面控件
- 仿天天动态上拉播放界面控件
- 上拉跳转界面,仿淘宝
- 仿网易主界面左右滑动以及顶部大图,下拉刷新.上拉加载更多等功能集成控件
- Android 仿iOS上拉下拉界面的效果实现
- popwindow显示可从上往下拉的仿抽屉式控件
- Android自定义控件(四)仿网易客户端上拉加载更多
- Android自定义控件(四)仿网易客户端上拉加载更多
- Android仿IOS布局上拉下拉回弹,并显示底层控件
- android 仿SwipeRefreshLayout实现下拉刷新上拉加载控件(PullRefreshLayout)
- Android自定义控件实战——实现仿IOS下拉刷新上拉加载 PullToRefreshLayout
- 仿淘宝上拉查看商品详情控件的源代码解读与应用
- POPwindow显示可从上往下拉的仿抽屉式控件
- Android仿网易云音乐播放界面
- Android仿网易云音乐播放界面
- MFC会播放的界面,动态界面
- csdnListView上拉加载上拉刷新界面
- 仿常见应用的下拉刷新,上拉加载的控件实现原理肉,例Android-PullToRefresh
- 深入理解异步任务AsyncTask
- 利用JspSmartUpload.jar上传出错:Files' name is invalid or does not exist (1205)
- 如何自定义COM异常
- SetTimer设置后没法调用定时器函数(回调函数)吗?
- IntelliJ IDEA 14 注册码
- 仿天天动态上拉播放界面控件
- 初用firefox开发者工具
- js将页面重定向与转发
- [Swift] Any VS AnyObject
- 手机屏幕分辨率单位
- 第4章导图
- Foundation框架第十二弹:NSFileManager的常用方法
- OC IOS开发使用半透明模糊效果方法合集
- oracle循环小结