ViewDragHelper实现QQ侧滑效果
程序员文章站
2023-12-10 14:54:58
前言
侧滑的实现方式有很多方式来实现,这次总结的viewdraghelper就是其中一种方式,v...
前言
侧滑的实现方式有很多方式来实现,这次总结的viewdraghelper就是其中一种方式,viewdraghelper是2013年谷歌i/o大会发布的新的控件,为了解决界面控件拖拽问题。下面就是自己学习写的一个实现类似于qq侧滑效果的实现。
activity_main.xml:
<com.yctc.drag.draglayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/dl" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/bg" tools:context=".mainactivity" > <linearlayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingbottom="50dp" android:paddingleft="10dp" android:paddingright="50dp" android:paddingtop="50dp" > <imageview android:layout_width="50dp" android:layout_height="50dp" android:src="@drawable/head" /> <listview android:id="@+id/lv_left" android:layout_width="match_parent" android:layout_height="match_parent" > </listview> </linearlayout> <com.yctc.drag.mylinearlayout android:id="@+id/mll" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:orientation="vertical" > <relativelayout android:layout_width="match_parent" android:layout_height="50dp" android:background="#18b6ef" android:gravity="center_vertical" > <imageview android:id="@+id/iv_header" android:layout_width="30dp" android:layout_height="30dp" android:layout_marginleft="15dp" android:src="@drawable/head" /> <textview android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerhorizontal="true" android:text="header" /> </relativelayout> <listview android:id="@+id/lv_main" android:layout_width="match_parent" android:layout_height="match_parent" > </listview> </com.yctc.drag.mylinearlayout> </com.yctc.drag.draglayout>
draglayout.java:
public class draglayout extends framelayout { private static final string tag = "tag"; private viewdraghelper mdraghelper; private viewgroup mleftcontent; private viewgroup mmaincontent; private ondragstatuschangelistener mlistener; private status mstatus = status.close; /** * 状态枚举 */ public static enum status { close, open, draging; } public interface ondragstatuschangelistener{ void onclose(); void onopen(); void ondraging(float percent); } public status getstatus() { return mstatus; } public void setstatus(status mstatus) { this.mstatus = mstatus; } public void setdragstatuslistener(ondragstatuschangelistener mlistener){ this.mlistener = mlistener; } public draglayout(context context) { this(context, null); } public draglayout(context context, attributeset attrs) { this(context, attrs, 0); } public draglayout(context context, attributeset attrs, int defstyle) { super(context, attrs, defstyle); // a.初始化 (通过静态方法) mdraghelper = viewdraghelper.create(this , mcallback); } viewdraghelper.callback mcallback = new viewdraghelper.callback() { // c. 重写事件 // 1. 根据返回结果决定当前child是否可以拖拽 // child 当前被拖拽的view // pointerid 区分多点触摸的id @override public boolean trycaptureview(view child, int pointerid) { log.d(tag, "trycaptureview: " + child); return true; }; @override public void onviewcaptured(view capturedchild, int activepointerid) { log.d(tag, "onviewcaptured: " + capturedchild); // 当capturedchild被捕获时,调用. super.onviewcaptured(capturedchild, activepointerid); } @override public int getviewhorizontaldragrange(view child) { // 返回拖拽的范围, 不对拖拽进行真正的限制. 仅仅决定了动画执行速度 return mrange; } // 2. 根据建议值 修正将要移动到的(横向)位置 (重要) // 此时没有发生真正的移动 public int clampviewpositionhorizontal(view child, int left, int dx) { // child: 当前拖拽的view // left 新的位置的建议值, dx 位置变化量 // left = oldleft + dx; log.d(tag, "clampviewpositionhorizontal: " + "oldleft: " + child.getleft() + " dx: " + dx + " left: " +left); if(child == mmaincontent){ left = fixleft(left); } return left; } // 3. 当view位置改变的时候, 处理要做的事情 (更新状态, 伴随动画, 重绘界面) // 此时,view已经发生了位置的改变 @override public void onviewpositionchanged(view changedview, int left, int top, int dx, int dy) { // changedview 改变位置的view // left 新的左边值 // dx 水平方向变化量 super.onviewpositionchanged(changedview, left, top, dx, dy); log.d(tag, "onviewpositionchanged: " + "left: " + left + " dx: " + dx); int newleft = left; if(changedview == mleftcontent){ // 把当前变化量传递给mmaincontent newleft = mmaincontent.getleft() + dx; } // 进行修正 newleft = fixleft(newleft); if(changedview == mleftcontent) { // 当左面板移动之后, 再强制放回去. mleftcontent.layout(0, 0, 0 + mwidth, 0 + mheight); mmaincontent.layout(newleft, 0, newleft + mwidth, 0 + mheight); } // 更新状态,执行动画 dispatchdragevent(newleft); // 为了兼容低版本, 每次修改值之后, 进行重绘 invalidate(); } // 4. 当view被释放的时候, 处理的事情(执行动画) @override public void onviewreleased(view releasedchild, float xvel, float yvel) { // view releasedchild 被释放的子view // float xvel 水平方向的速度, 向右为+ // float yvel 竖直方向的速度, 向下为+ log.d(tag, "onviewreleased: " + "xvel: " + xvel + " yvel: " + yvel); super.onviewreleased(releasedchild, xvel, yvel); // 判断执行 关闭/开启 // 先考虑所有开启的情况,剩下的就都是关闭的情况 if(xvel == 0 && mmaincontent.getleft() > mrange / 2.0f){ open(); }else if (xvel > 0) { open(); }else { close(); } } @override public void onviewdragstatechanged(int state) { // todo auto-generated method stub super.onviewdragstatechanged(state); } }; /** * 根据范围修正左边值 * @param left * @return */ private int fixleft(int left) { if(left < 0){ return 0; }else if (left > mrange) { return mrange; } return left; } protected void dispatchdragevent(int newleft) { float percent = newleft * 1.0f/ mrange; //0.0f -> 1.0f log.d(tag, "percent: " + percent); if(mlistener != null){ mlistener.ondraging(percent); } // 更新状态, 执行回调 status prestatus = mstatus; mstatus = updatestatus(percent); if(mstatus != prestatus){ // 状态发生变化 if(mstatus == status.close){ // 当前变为关闭状态 if(mlistener != null){ mlistener.onclose(); } }else if (mstatus == status.open) { if(mlistener != null){ mlistener.onopen(); } } } // * 伴随动画: animviews(percent); } private status updatestatus(float percent) { if(percent == 0f){ return status.close; }else if (percent == 1.0f) { return status.open; } return status.draging; } private void animviews(float percent) { // > 1. 左面板: 缩放动画, 平移动画, 透明度动画 // 缩放动画 0.0 -> 1.0 >>> 0.5f -> 1.0f >>> 0.5f * percent + 0.5f // mleftcontent.setscalex(0.5f + 0.5f * percent); // mleftcontent.setscaley(0.5f + 0.5f * percent); viewhelper.setscalex(mleftcontent, evaluate(percent, 0.5f, 1.0f)); viewhelper.setscaley(mleftcontent, 0.5f + 0.5f * percent); // 平移动画: -mwidth / 2.0f -> 0.0f viewhelper.settranslationx(mleftcontent, evaluate(percent, -mwidth / 2.0f, 0)); // 透明度: 0.5 -> 1.0f viewhelper.setalpha(mleftcontent, evaluate(percent, 0.5f, 1.0f)); // > 2. 主面板: 缩放动画 // 1.0f -> 0.8f viewhelper.setscalex(mmaincontent, evaluate(percent, 1.0f, 0.8f)); viewhelper.setscaley(mmaincontent, evaluate(percent, 1.0f, 0.8f)); // > 3. 背景动画: 亮度变化 (颜色变化) getbackground().setcolorfilter((integer)evaluatecolor(percent, color.black, color.transparent), mode.src_over); } /** * 估值器 * @param fraction * @param startvalue * @param endvalue * @return */ public float evaluate(float fraction, number startvalue, number endvalue) { float startfloat = startvalue.floatvalue(); return startfloat + fraction * (endvalue.floatvalue() - startfloat); } /** * 颜色变化过度 * @param fraction * @param startvalue * @param endvalue * @return */ public object evaluatecolor(float fraction, object startvalue, object endvalue) { int startint = (integer) startvalue; int starta = (startint >> 24) & 0xff; int startr = (startint >> 16) & 0xff; int startg = (startint >> 8) & 0xff; int startb = startint & 0xff; int endint = (integer) endvalue; int enda = (endint >> 24) & 0xff; int endr = (endint >> 16) & 0xff; int endg = (endint >> 8) & 0xff; int endb = endint & 0xff; return (int)((starta + (int)(fraction * (enda - starta))) << 24) | (int)((startr + (int)(fraction * (endr - startr))) << 16) | (int)((startg + (int)(fraction * (endg - startg))) << 8) | (int)((startb + (int)(fraction * (endb - startb)))); } @override public void computescroll() { super.computescroll(); // 2. 持续平滑动画 (高频率调用) if(mdraghelper.continuesettling(true)){ // 如果返回true, 动画还需要继续执行 viewcompat.postinvalidateonanimation(this); } } public void close(){ close(true); } /** * 关闭 */ public void close(boolean issmooth) { int finalleft = 0; if(issmooth){ // 1. 触发一个平滑动画 if(mdraghelper.smoothslideviewto(mmaincontent, finalleft, 0)){ // 返回true代表还没有移动到指定位置, 需要刷新界面. // 参数传this(child所在的viewgroup) viewcompat.postinvalidateonanimation(this); } }else { mmaincontent.layout(finalleft, 0, finalleft + mwidth, 0 + mheight); } } public void open(){ open(true); } /** * 开启 */ public void open(boolean issmooth) { int finalleft = mrange; if(issmooth){ // 1. 触发一个平滑动画 if(mdraghelper.smoothslideviewto(mmaincontent, finalleft, 0)){ // 返回true代表还没有移动到指定位置, 需要刷新界面. // 参数传this(child所在的viewgroup) viewcompat.postinvalidateonanimation(this); } }else { mmaincontent.layout(finalleft, 0, finalleft + mwidth, 0 + mheight); } } private int mheight; private int mwidth; private int mrange; // b.传递触摸事件 @override public boolean onintercepttouchevent(motionevent ev) { // 传递给mdraghelper return mdraghelper.shouldintercepttouchevent(ev); } @override public boolean ontouchevent(motionevent event) { try { mdraghelper.processtouchevent(event); } catch (exception e) { e.printstacktrace(); } // 返回true, 持续接受事件 return true; } @override protected void onfinishinflate() { super.onfinishinflate(); // github // 写注释 // 容错性检查 (至少有俩子view, 子view必须是viewgroup的子类) if(getchildcount() < 2){ throw new illegalstateexception("布局至少有俩孩子. your viewgroup must have 2 children at least."); } if(!(getchildat(0) instanceof viewgroup && getchildat(1) instanceof viewgroup)){ throw new illegalargumentexception("子view必须是viewgroup的子类. your children must be an instance of viewgroup"); } mleftcontent = (viewgroup) getchildat(0); mmaincontent = (viewgroup) getchildat(1); } @override protected void onsizechanged(int w, int h, int oldw, int oldh) { super.onsizechanged(w, h, oldw, oldh); // 当尺寸有变化的时候调用 mheight = getmeasuredheight(); mwidth = getmeasuredwidth(); // 移动的范围 mrange = (int) (mwidth * 0.6f); } }
mylineatlayout.java:
public class mylinearlayout extends linearlayout { private draglayout mdraglayout; public mylinearlayout(context context) { super(context); } public mylinearlayout(context context, attributeset attrs) { super(context, attrs); } public void setdraglayout(draglayout mdraglayout){ this.mdraglayout = mdraglayout; } @override public boolean onintercepttouchevent(motionevent ev) { // 如果当前是关闭状态, 按之前方法判断 if(mdraglayout.getstatus() == status.close){ return super.onintercepttouchevent(ev); }else { return true; } } @override public boolean ontouchevent(motionevent event) { // 如果当前是关闭状态, 按之前方法处理 if(mdraglayout.getstatus() == status.close){ return super.ontouchevent(event); }else { // 手指抬起, 执行关闭操作 if(event.getaction() == motionevent.action_up){ mdraglayout.close(); } return true; } } }
mainactivity.java:
public class mainactivity extends activity { private static final string tag = "tag"; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); requestwindowfeature(window.feature_no_title); setcontentview(r.layout.activity_main); final listview mleftlist = (listview) findviewbyid(r.id.lv_left); final listview mmainlist = (listview) findviewbyid(r.id.lv_main); final imageview mheaderimage = (imageview) findviewbyid(r.id.iv_header); mylinearlayout mlinearlayout = (mylinearlayout) findviewbyid(r.id.mll); // 查找draglayout, 设置监听 draglayout mdraglayout = (draglayout) findviewbyid(r.id.dl); // 设置引用 mlinearlayout.setdraglayout(mdraglayout); mdraglayout.setdragstatuslistener(new ondragstatuschangelistener() { @override public void onopen() { utils.showtoast(mainactivity.this, "onopen"); // 左面板listview随机设置一个条目 random random = new random(); int nextint = random.nextint(50); mleftlist.smoothscrolltoposition(nextint); } @override public void ondraging(float percent) { log.d(tag, "ondraging: " + percent);// 0 -> 1 // 更新图标的透明度 // 1.0 -> 0.0 viewhelper.setalpha(mheaderimage, 1 - percent); } @override public void onclose() { utils.showtoast(mainactivity.this, "onclose"); // 让图标晃动 // mheaderimage.settranslationx(translationx) objectanimator manim = objectanimator.offloat(mheaderimage, "translationx", 15.0f); manim.setinterpolator(new cycleinterpolator(4)); manim.setduration(500); manim.start(); } }); mleftlist.setadapter(new arrayadapter<string>(this, android.r.layout.simple_list_item_1, cheeses.scheesestrings){ @override public view getview(int position, view convertview, viewgroup parent) { view view = super.getview(position, convertview, parent); textview mtext = ((textview)view); mtext.settextcolor(color.white); return view; } }); mmainlist.setadapter(new arrayadapter<string>(this, android.r.layout.simple_list_item_1, cheeses.names)) } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。