欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android App中实现向右滑动销毁功能的要点解析

程序员文章站 2024-02-29 08:46:46
今天给大家带来一个向右滑动销毁activity的效果,activtiy随着手指的移动而移动,该效果在android应用中还是比较少见的,在ios中就比较常见了,例如“网易新...

今天给大家带来一个向右滑动销毁activity的效果,activtiy随着手指的移动而移动,该效果在android应用中还是比较少见的,在ios中就比较常见了,例如“网易新闻” ,"美食杰" , "淘宝"等应用采用此效果,而android应用中“知乎”采用的也是这种滑动切换activity的效果, 不过我发现“淘宝”并没有随着手势的移动而移动,只是捕捉到滑动手势,然后产生平滑切换界面的动画效果,这个在android中还是很好实现的,  网上很多滑动切换activity的demo貌似都是这种效果的吧,如果要实现类似“网易新闻”的随手势的滑动而滑动,似乎就要复杂一些了,我之前在ios中看到"网易新闻"的这种效果就很感兴趣,然后群里也有朋友问我怎么实现类似“知乎”这个应用的滑动切换的效果,我也特意去下了一个“知乎”,在之前的实现中我遇到了一些瓶颈,没有实现出来就搁置了在那里,今天无意中看到给activity设置透明的背景,于是乎我恍然大悟,真是灵感来源于瞬间,不能强求啊,然后自己就将此效果实现了出来,给大家分享一下,希望给有此需求的你一点点帮助。
不知道大家对scroller这个类以及view的scrollby() 和scrollto()的使用熟悉不?我之前介绍了scroller类的滑动实现原理android 带你从源码的角度解析scroller的滚动实现原理,在那里面也介绍了scrollby() 和scrollto()方法,不明白的同学可以去看看,这对实现此效果有很大的帮助,了解scrollby() 和scrollto()的朋友应该知道,如果想对某个view(例如button)就行滚动,我们直接调用该view(button)的scrollby()方法,并不是该view(button)进行滚动,而是该view里面的内容(button上面的文字)进行滚动,所以我们假如要让view整体滚动就需要对其view的父布局调用scrollby()方法,回到这篇文章来,假如我们想要对一个activity进行滚动,我们就需求对这个activity布局文件的顶层布局的父布局进行滚动
例如下面的xml布局文件

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" 
  xmlns:tools="http://schemas.android.com/tools" 
  android:layout_width="match_parent" 
  android:layout_height="match_parent" 
  android:gravity="center" 
  android:orientation="vertical" > 
 
 
</linearlayout> 

如果我们对linearlayout进行滚动,并不能实现我们想要的效果,而只能对linearlayout里面的内容或者说是子view进行滚动,所以我们需要获取利用linearlayout的getparent()方法获取父布局,其实android系统会对我们的布局文件的最外层套一个framelayout,所以我们其实就是对framelayout进行滚动就行了
了解了实现的原理之后,我们就来编写代码吧,首先新建一个android工程,取名sildingfinish
由于我们的需求可能不是在一个界面提供这个滑动切换的效果,所以我们应该将这部分滑动的逻辑抽取出来,我这里就他写成了一个扩展relativelayout的自定义布局sildingfinishlayout,首先我们看其代码

package com.example.view; 
 
import android.content.context; 
import android.util.attributeset; 
import android.view.motionevent; 
import android.view.view; 
import android.view.view.ontouchlistener; 
import android.view.viewconfiguration; 
import android.view.viewgroup; 
import android.widget.abslistview; 
import android.widget.relativelayout; 
import android.widget.scrollview; 
import android.widget.scroller; 
 
/** 
 * 自定义可以滑动的relativelayout, 类似于ios的滑动删除页面效果,当我们要使用 
 * 此功能的时候,需要将该activity的顶层布局设置为sildingfinishlayout, 
 * 然后需要调用settouchview()方法来设置需要滑动的view 
 * 
 * @author xiaanming 
 * 
 * @blog http://blog.csdn.net/xiaanming 
 * 
 */ 
public class sildingfinishlayout extends relativelayout implements 
    ontouchlistener { 
  /** 
   * sildingfinishlayout布局的父布局 
   */ 
  private viewgroup mparentview; 
  /** 
   * 处理滑动逻辑的view 
   */ 
  private view touchview; 
  /** 
   * 滑动的最小距离 
   */ 
  private int mtouchslop; 
  /** 
   * 按下点的x坐标 
   */ 
  private int downx; 
  /** 
   * 按下点的y坐标 
   */ 
  private int downy; 
  /** 
   * 临时存储x坐标 
   */ 
  private int tempx; 
  /** 
   * 滑动类 
   */ 
  private scroller mscroller; 
  /** 
   * sildingfinishlayout的宽度 
   */ 
  private int viewwidth; 
  /** 
   * 记录是否正在滑动 
   */ 
  private boolean issilding; 
   
  private onsildingfinishlistener onsildingfinishlistener; 
  private boolean isfinish; 
   
 
  public sildingfinishlayout(context context, attributeset attrs) { 
    this(context, attrs, 0); 
  } 
 
  public sildingfinishlayout(context context, attributeset attrs, int defstyle) { 
    super(context, attrs, defstyle); 
 
    mtouchslop = viewconfiguration.get(context).getscaledtouchslop(); 
    mscroller = new scroller(context); 
  } 
 
  @override 
  protected void onlayout(boolean changed, int l, int t, int r, int b) { 
    super.onlayout(changed, l, t, r, b); 
    if (changed) { 
      // 获取sildingfinishlayout所在布局的父布局 
      mparentview = (viewgroup) this.getparent(); 
      viewwidth = this.getwidth(); 
    } 
  } 
 
  /** 
   * 设置onsildingfinishlistener, 在onsildingfinish()方法中finish activity 
   * 
   * @param onsildingfinishlistener 
   */ 
  public void setonsildingfinishlistener( 
      onsildingfinishlistener onsildingfinishlistener) { 
    this.onsildingfinishlistener = onsildingfinishlistener; 
  } 
 
  /** 
   * 设置touch的view 
   * 
   * @param touchview 
   */ 
  public void settouchview(view touchview) { 
    this.touchview = touchview; 
    touchview.setontouchlistener(this); 
  } 
 
  public view gettouchview() { 
    return touchview; 
  } 
 
  /** 
   * 滚动出界面 
   */ 
  private void scrollright() { 
    final int delta = (viewwidth + mparentview.getscrollx()); 
    // 调用startscroll方法来设置一些滚动的参数,我们在computescroll()方法中调用scrollto来滚动item 
    mscroller.startscroll(mparentview.getscrollx(), 0, -delta + 1, 0, 
        math.abs(delta)); 
    postinvalidate(); 
  } 
 
  /** 
   * 滚动到起始位置 
   */ 
  private void scrollorigin() { 
    int delta = mparentview.getscrollx(); 
    mscroller.startscroll(mparentview.getscrollx(), 0, -delta, 0, 
        math.abs(delta)); 
    postinvalidate(); 
  } 
 
  /** 
   * touch的view是否是abslistview, 例如listview, gridview等其子类 
   * 
   * @return 
   */ 
  private boolean istouchonabslistview() { 
    return touchview instanceof abslistview ? true : false; 
  } 
 
  /** 
   * touch的view是否是scrollview或者其子类 
   * 
   * @return 
   */ 
  private boolean istouchonscrollview() { 
    return touchview instanceof scrollview ? true : false; 
  } 
 
  @override 
  public boolean ontouch(view v, motionevent event) { 
    switch (event.getaction()) { 
    case motionevent.action_down: 
      downx = tempx = (int) event.getrawx(); 
      downy = (int) event.getrawy(); 
      break; 
    case motionevent.action_move: 
      int movex = (int) event.getrawx(); 
      int deltax = tempx - movex; 
      tempx = movex; 
      if (math.abs(movex - downx) > mtouchslop 
          && math.abs((int) event.getrawy() - downy) < mtouchslop) { 
        issilding = true; 
 
        // 若touchview是abslistview, 
        // 则当手指滑动,取消item的点击事件,不然我们滑动也伴随着item点击事件的发生 
        if (istouchonabslistview()) { 
          motionevent cancelevent = motionevent.obtain(event); 
          cancelevent 
              .setaction(motionevent.action_cancel 
                  | (event.getactionindex() << motionevent.action_pointer_index_shift)); 
          v.ontouchevent(cancelevent); 
        } 
 
      } 
 
      if (movex - downx >= 0 && issilding) { 
        mparentview.scrollby(deltax, 0); 
 
        // 屏蔽在滑动过程中listview scrollview等自己的滑动事件 
        if (istouchonscrollview() || istouchonabslistview()) { 
          return true; 
        } 
      } 
      break; 
    case motionevent.action_up: 
      issilding = false; 
      if (mparentview.getscrollx() <= -viewwidth / 2) { 
        isfinish = true; 
        scrollright(); 
      } else { 
        scrollorigin(); 
        isfinish = false; 
      } 
      break; 
    } 
 
    // 假如touch的view是abslistview或者scrollview 我们处理完上面自己的逻辑之后 
    // 再交给abslistview, scrollview自己处理其自己的逻辑 
    if (istouchonscrollview() || istouchonabslistview()) { 
      return v.ontouchevent(event); 
    } 
 
    // 其他的情况直接返回true 
    return true; 
  } 
 
  @override 
  public void computescroll() { 
    // 调用startscroll的时候scroller.computescrolloffset()返回true, 
    if (mscroller.computescrolloffset()) { 
      mparentview.scrollto(mscroller.getcurrx(), mscroller.getcurry()); 
      postinvalidate(); 
 
      if (mscroller.isfinished()) { 
 
        if (onsildingfinishlistener != null && isfinish) { 
          onsildingfinishlistener.onsildingfinish(); 
        } 
      } 
    } 
  } 
   
 
  public interface onsildingfinishlistener { 
    public void onsildingfinish(); 
  } 
 
} 

我们在onlayout()方法中利用getparent()方法获取该布局的父布局和获取其控件的宽度,主要是为之后的实现做准备工作。
我们的滑动逻辑主要是利用view的scrollby() 方法, scrollto()方法和scroller类来实现的,当手指拖动视图的时候,我们监听手指在屏幕上滑动的距离利用view的scrollby() 方法使得view随着手指的滑动而滑动,而当手指离开屏幕,我们在根据逻辑使用scroller类startscroll()方法设置滑动的参数,然后再根据view的scrollto进行滚动。
对于view的滑动,存在一些touch事件消费的处理等问题,因此我们需要对view的整个touch事件很熟悉 ,最主要的就是activity里面有一些listview、 gridview、scrollview等控件了, 假如我们activity里面存在listview、gridview等控件的话,我们对activity的最外层布局进行滚动根本就无效果,因为touch事件被listview、gridview等控件消费了,所以activity的最外层布局根本得不到touch事件,也就实现不了touch逻辑了,所以为了解决此touch事件问题我提供了settouchview(view touchview) 方法,这个方法是将touch事件动态的设置到到view上面,所以针对上面的问题,我们将ontouchlistener直接设置到listview、gridview上面,这样子就避免了activity的最外层接受不到touch事件的问题了

接下来看ontouch()方法
首先我们在action_down记录按下点的x,y坐标
然后在action_move中判断,如果我们在水平方向滑动的距离大于mtouchslop并且在竖直方向滑动的距离小于mtouchslop,表示activity处于滑动状态,我们判断如果touchview是listview、gridview或者其子类的时候,因为我们手指在listview、gridview上面,伴随着item的点击事件的发生,所以我们对touchview设置action_cancel来取消item的点击事件,然后对该布局的父布局调用scrollby()进行滚动,并且如果touchview是abslistview或者scrollview直接返回true,来取消abslistview或者scrollview本身的action_move事件,最直观的感受就是我们在滑动activity的时候,禁止abslistview或者scrollview的上下滑动
最后在action_up中判断如果手指滑动的距离大于控件长度的二分之一,表示将activity滑出界面,否则滑动到起始位置,我们利用scroller类的startscroll()方法设置好开始位置,滑动距离和时间,然后调用postinvalidate()刷新界面,之后就到computescroll()方法中,我们利用scrollto()方法对该布局的父布局进行滚动,滚动结束之后,我们判断界面是否滑出界面,如果是就调用onsildingfinishlistener接口的onsildingfinish()方法,所以只要在onsildingfinish()方法中finish界面就行了
整个滑动布局的代码就是这个样子,接下来我们就来使用了,主界面activity只有三个按钮,分别跳转到普通布局的activity,有listview的activity和有scrollview的activity中

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" 
  xmlns:tools="http://schemas.android.com/tools" 
  android:layout_width="match_parent" 
  android:layout_height="match_parent" 
  android:gravity="center" 
  android:orientation="vertical" > 
 
  <button 
    android:id="@+id/normal_activity" 
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:text="普通的activity" /> 
 
  <button 
    android:id="@+id/abslistview_activity" 
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:text="有abslistview的activity" /> 
 
  <button 
    android:id="@+id/scrollview_activity" 
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:text="有scrollview的activity" /> 
 
</linearlayout> 

 
然后就是mainactivity的代码,根据id实例化button,然后为button设置onclicklistener事件,不同的按钮跳转到不同的activity,然后设置从右向左滑动的动画,重写onbackpressed()方法,当我们按下手机物理键盘的返回键,添加从左向右滑出的动画

package com.example.slidingfinish; 
 
import android.app.activity; 
import android.content.intent; 
import android.os.bundle; 
import android.view.view; 
import android.view.view.onclicklistener; 
import android.view.window; 
import android.widget.button; 
 
import com.example.slidingfinish.r; 
 
public class mainactivity extends activity implements onclicklistener { 
 
  @override 
  protected void oncreate(bundle savedinstancestate) { 
    requestwindowfeature(window.feature_no_title); 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.activity_main); 
 
    button mbuttonnormal = (button) findviewbyid(r.id.normal_activity); 
    mbuttonnormal.setonclicklistener(this); 
 
    button mbuttonabs = (button) findviewbyid(r.id.abslistview_activity); 
    mbuttonabs.setonclicklistener(this); 
 
    button mbuttonscroll = (button) findviewbyid(r.id.scrollview_activity); 
    mbuttonscroll.setonclicklistener(this); 
 
  } 
 
  @override 
  public void onclick(view v) { 
    intent mintent = null; 
    switch (v.getid()) { 
    case r.id.normal_activity: 
      mintent = new intent(mainactivity.this, normalactivity.class); 
      break; 
    case r.id.abslistview_activity: 
      mintent = new intent(mainactivity.this, absactivity.class); 
      break; 
    case r.id.scrollview_activity: 
      mintent = new intent(mainactivity.this, scrollactivity.class); 
      break; 
    } 
 
    startactivity(mintent); 
    overridependingtransition(r.anim.base_slide_right_in, r.anim.base_slide_remain); 
  } 
   
  //press the back button in mobile phone 
  @override 
  public void onbackpressed() { 
    super.onbackpressed(); 
    overridependingtransition(0, r.anim.base_slide_right_out); 
  } 
 
} 

在这里我之贴出含有listview的activity的代码,先看布局,我们自定义滑动布局sildingfinishlayout应该放在xml的最顶层

<?xml version="1.0" encoding="utf-8"?> 
<com.example.view.sildingfinishlayout xmlns:android="http://schemas.android.com/apk/res/android" 
  xmlns:tools="http://schemas.android.com/tools" 
  android:id="@+id/sildingfinishlayout" 
  android:layout_width="match_parent" 
  android:layout_height="match_parent" 
  android:background="#556677" > 
 
  <listview 
    android:id="@+id/listview" 
    android:cachecolorhint="@android:color/transparent" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" > 
  </listview> 
   
   
</com.example.view.sildingfinishlayout> 

package com.example.slidingfinish; 
 
import java.util.arraylist; 
import java.util.list; 
 
import android.app.activity; 
import android.content.intent; 
import android.os.bundle; 
import android.view.view; 
import android.view.window; 
import android.widget.adapterview; 
import android.widget.adapterview.onitemclicklistener; 
import android.widget.arrayadapter; 
import android.widget.listview; 
 
import com.example.slidingfinish.r; 
import com.example.view.sildingfinishlayout; 
import com.example.view.sildingfinishlayout.onsildingfinishlistener; 
 
public class absactivity extends activity { 
  private list<string> list = new arraylist<string>(); 
 
  @override 
  protected void oncreate(bundle savedinstancestate) { 
    requestwindowfeature(window.feature_no_title); 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.activity_abslistview); 
 
    for (int i = 0; i <= 30; i++) { 
      list.add("测试数据" + i); 
    } 
 
    listview mlistview = (listview) findviewbyid(r.id.listview); 
    arrayadapter<string> adapter = new arrayadapter<string>( 
        absactivity.this, android.r.layout.simple_list_item_1, list); 
    mlistview.setadapter(adapter); 
 
    sildingfinishlayout msildingfinishlayout = (sildingfinishlayout) findviewbyid(r.id.sildingfinishlayout); 
    msildingfinishlayout 
        .setonsildingfinishlistener(new onsildingfinishlistener() { 
 
          @override 
          public void onsildingfinish() { 
            absactivity.this.finish(); 
          } 
        }); 
 
    // touchview要设置到listview上面 
    msildingfinishlayout.settouchview(mlistview); 
 
    mlistview.setonitemclicklistener(new onitemclicklistener() { 
 
      @override 
      public void onitemclick(adapterview<?> parent, view view, 
          int position, long id) { 
 
        startactivity(new intent(absactivity.this, normalactivity.class)); 
        overridependingtransition(r.anim.base_slide_right_in, 
            r.anim.base_slide_remain); 
      } 
    }); 
  } 
 
  // press the back button in mobile phone 
  @override 
  public void onbackpressed() { 
    super.onbackpressed(); 
    overridependingtransition(0, r.anim.base_slide_right_out); 
  } 
 
} 

利用id找到sildingfinishlayout实例,利用settouchview()方法设置touchview到listview上面,然后调用setonsildingfinishlistener()设置onsildingfinishlistener,在onsildingfinish()中finish界面就可以啦。
在运行项目之前还有一个很重要的操作,也是之前我被卡到的问题,就是我们需要对activity设置为透明,即设置主题android:theme="@android:style/theme.translucent"

<activity 
      android:name=".absactivity" 
      android:theme="@android:style/theme.translucent" > 
    </activity> 
    <activity 
      android:name=".normalactivity" 
      android:theme="@android:style/theme.translucent" > 
    </activity> 
    <activity 
      android:name=".scrollactivity" 
      android:theme="@android:style/theme.translucent" > 
    </activity> 

 
好了,现在我们可以运行项目看看效果啦

Android App中实现向右滑动销毁功能的要点解析

正是我们想要的效果,如果想要加入滑动切换界面的效果只需要三步就行了,首先将activity布局的最外层修改为sildingfinishlayout,然后在activity里面调用settouchview()方法设置touchview,设置onsildingfinishlistener监听在onsildingfinish()方法中finish界面,最后设置activity的背景为透明(不是设置activity布局文件的最顶层布局背景颜色透明,这点要区分一下)是不是很方便呢?好了,今天的讲解到这里就结束了~