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

Android实现局部图片滑动指引效果示例

程序员文章站 2024-03-04 08:49:23
今天发布本文的原因是应一个网友要求,就是实现局部的图片滑动指引效果。这种效果一般是在新闻客户端上比较常见,其功能是: 1、顶部单张图片左右拖拉滑动; 2、带指引...

今天发布本文的原因是应一个网友要求,就是实现局部的图片滑动指引效果。这种效果一般是在新闻客户端上比较常见,其功能是:

1、顶部单张图片左右拖拉滑动;

2、带指引;

3、仅滑动顶部单张图片,不滑动页面,下面的图文内容不动;

4、类似于新闻客户端的功能

为了大家能更好的理解,我们先来看下要实现的效果图:

Android实现局部图片滑动指引效果示例

以上便是实现的效果图,其实实现原理也并不难,我们只需要将android-support-v4.jar包中viewpager控件设置成局部就可以,只是处理界面时稍微有点麻烦,不过看完本篇之后,大家以后使用时直接调用就行。也希望本篇能够对大家有所帮助。

好了,下面让我们开始我们的实现过程,主要给大家介绍一下实现步骤和一些核心代码。首先我们需要将android-support-v4.jar添加到工程当中,然后让我们看一下程序结构:

Android实现局部图片滑动指引效果示例

我先简要介绍其实现原理:

在布局页面中将设置成局部,限制其高度,然后为滑动的图片集合生成布局界面,并在代码中设置相应的数据适配器和监听事件。在切换事件监听器中更改相应的圆点图片和显示标题,由于滑动图片下方的界面不需要改变内容,所以很很容易内容超过屏幕,所以需要设置scrollview以在内容比较多时显示滚动条,我会在下面介绍如何让viewpager和scrollview结合使用。

先看下android.support.v4.view.viewpager在布局界面中的核心代码:

<android.support.v4.view.viewpager
    android:id="@+id/image_slide_page"
    android:layout_width="fill_parent"
    android:layout_height="180dip"
    android:focusable="true" />

在程序结构中,mainactivity.java是启动的activity,而topicnews.java是显示头条的acitivity。在显示时,我们需要将topicnews.java中的对象进行初始化设置,如下代码:

/**
   * 初始化
   */
  private void initeviews(){
    // 滑动图片区域
    imagepageviews = new arraylist<view>();
    layoutinflater inflater = getlayoutinflater(); 
    main = (viewgroup)inflater.inflate(r.layout.page_topic_news, null);
    viewpager = (viewpager) main.findviewbyid(r.id.image_slide_page); 
    
    // 圆点图片区域
    parser = new newsxmlparser();
    int length = parser.getslideimages().length;
    imagecircleviews = new imageview[length];
    imagecircleview = (viewgroup) main.findviewbyid(r.id.layout_circle_images);
    slidelayout = new slideimagelayout(topicnews.this);
    slidelayout.setcircleimagelayout(length);
    
    for(int i = 0;i < length;i++){
      imagepageviews.add(slidelayout.getslideimagelayout(parser.getslideimages()[i]));
      imagecircleviews[i] = slidelayout.getcircleimagelayout(i);
      imagecircleview.addview(slidelayout.getlinearlayout(imagecircleviews[i], 10, 10));
    }
    
    // 设置默认的滑动标题
    tvslidetitle = (textview) main.findviewbyid(r.id.tvslidetitle);
    tvslidetitle.settext(parser.getslidetitles()[0]);
    
    setcontentview(main);
    
    // 设置viewpager
    viewpager.setadapter(new slideimageadapter()); 
    viewpager.setonpagechangelistener(new imagepagechangelistener());
  }

以上对象的声明代码如下所示:

// 滑动图片的集合
  private arraylist<view> imagepageviews = null;
  private viewgroup main = null;
  private viewpager viewpager = null;
  // 当前viewpager索引
  private int pageindex = 0; 
  
  // 包含圆点图片的view
  private viewgroup imagecircleview = null;
  private imageview[] imagecircleviews = null; 
  
  // 滑动标题
  private textview tvslidetitle = null;
  
  // 布局设置类
  private slideimagelayout slidelayout = null;
  // 数据解析类
  private newsxmlparser parser = null;

由于在显示头条的activity即topicnews中,设置布局文件不是直接设置的,也就是通过inflate将layout转化为view控件的,所以在使用page_topic_news.xml中的view时,需要通过main.findviewbyid(),即如下代码所示:
 

main = (viewgroup)inflater.inflate(r.layout.page_topic_news, null);
 viewpager = (viewpager) main.findviewbyid(r.id.image_slide_page); 

而不能像这样直接使用:

viewpager = (viewpager) findviewbyid(r.id.image_slide_page);

这点大家在使用时需要注意。

newsxmlparser类是用于对显示的数据进行解析,由于本示例只是一个演示示例,所以在这个类里我只是设置一些要显示的固定数据,没有设置动态数据,这点明白就可以,代码如下:

package com.image.indicator.parser;

import java.io.inputstream;
import java.util.hashmap;
import java.util.list;

import org.xmlpull.v1.xmlpullparser;

import android.util.xml;

import com.image.indicator.r;
import com.image.indicator.entity.news;
import com.image.indicator.utility.fileaccess;

/**
 * 解析新闻数据列表
 * @description: 解析新闻数据列表,这里只是个示例,具体地不再实现。

 * @file: newsxmlparser.java

 * @package com.image.indicator.parser

 * @author hanyonglu

 * @date 2012-6-18 下午02:31:26

 * @version v1.0
 */
public class newsxmlparser {
  // 新闻列表
  private list<hashmap<string, news>> newslist = null;
  
  // 滑动图片的集合,这里设置成了固定加载,当然也可动态加载。
  private int[] slideimages = {
      r.drawable.image01,
      r.drawable.image02,
      r.drawable.image03,
      r.drawable.image04,
      r.drawable.image05};
  
  // 滑动标题的集合
  private int[] slidetitles = {
      r.string.title1,
      r.string.title2,
      r.string.title3,
      r.string.title4,
      r.string.title5,
  };
  
  // 滑动链接的集合
  private string[] slideurls = {
      "http://mobile.csdn.net/a/20120616/2806676.html",
      "http://cloud.csdn.net/a/20120614/2806646.html",
      "http://mobile.csdn.net/a/20120613/2806603.html",
      "http://news.csdn.net/a/20120612/2806565.html",
      "http://mobile.csdn.net/a/20120615/2806659.html",
  };
  
  public int[] getslideimages(){
    return slideimages;
  }
  
  public int[] getslidetitles(){
    return slidetitles;
  }
  
  public string[] getslideurls(){
    return slideurls;
  }
  
  /**
   * 获取xmlpullparser对象
   * @param result
   * @return
   */
  private xmlpullparser getxmlpullparser(string result){
    xmlpullparser parser = xml.newpullparser();
    inputstream inputstream = fileaccess.string2inputstream(result);
    
    try {
      parser.setinput(inputstream, "utf-8");
    } catch (exception e) {
      // todo: handle exception
      e.printstacktrace();
    }
    
    return parser;
  }
  
  public int getnewslistcount(string result){
    int count = -1;
    
    try {
      xmlpullparser parser = getxmlpullparser(result);
      int event = parser.geteventtype();//产生第一个事件
      
      while(event != xmlpullparser.end_document){
        switch(event){
        case xmlpullparser.start_document:
          break;
        case xmlpullparser.start_tag://判断当前事件是否是标签元素开始事件
          if("count".equals(parser.getname())){//判断开始标签元素是否是count
            count = integer.parseint(parser.nexttext());
          }
          
          break;
        case xmlpullparser.end_tag://判断当前事件是否是标签元素结束事件
//          if("count".equals(parser.getname())){//判断开始标签元素是否是count
//            count = integer.parseint(parser.nexttext());
//          }
          
          break;
        }
      
        event = parser.next();//进入下一个元素并触发相应事件
      }
    } catch (exception e) {
      // todo: handle exception
      e.printstacktrace();
    }
    
    // 无返回值,则返回-1
    return count;
  }
}

关于newsxmlparser这个类,实现比较简单,不再详述,有兴趣的朋友可以在开发过程中将其设置成动态数据并进行解析。

刚才在上面介绍其实现原理时,我提到需要设置滑动图片集合的布局界面,那么如何设置其布局呢?这里我们需要用到slideimagelayout。

slideimagelayout类是用于生成滑动图片区域布局和圆点图片布局的类。我在上面的代码中(即在topicnews.java的初始化方法initeviews())使用for循环设置滑动图片及圆点图片的布局。在循环中就用到了getslideimagelayout()、getcircleimagelayout()和getlinearlayout()这几个方法。下面分别看下其功能,先看下getslideimagelayout()实现代码:

/**
   * 生成滑动图片区域布局
   * @param index
   * @return
   */
  public view getslideimagelayout(int index){
    // 包含textview的linearlayout
    linearlayout imagelinerlayout = new linearlayout(activity);
    linearlayout.layoutparams imagelinerlayoutparames = new linearlayout.layoutparams(
        linearlayout.layoutparams.wrap_content, 
        linearlayout.layoutparams.wrap_content,
        1);
    
    imageview iv = new imageview(activity);
    iv.setbackgroundresource(index);
    iv.setonclicklistener(new imageonclicklistener());
    imagelinerlayout.addview(iv,imagelinerlayoutparames);
    imagelist.add(iv);
    
    return imagelinerlayout;
  }

由于滑动图片一般需要设置其链接或是相应的id,以便在点击时转向相应的activity,显示相应的内容或详细信息。这里我没有过多的设置,只是在点击时显示标题及链接地址,代码如下:

// 滑动页面点击事件监听器
  private class imageonclicklistener implements onclicklistener{
    @override
    public void onclick(view v) {
      // todo auto-generated method stub
      toast.maketext(activity, parser.getslidetitles()[pageindex], toast.length_short).show();
      toast.maketext(activity, parser.getslideurls()[pageindex], toast.length_short).show();
    }
  }

getcircleimagelayout()方法主要是为圆点图片生成相应的imageview对象,代码如下:

/**
   * 生成圆点图片区域布局对象
   * @param index
   * @return
   */
  public imageview getcircleimagelayout(int index){
    imageview = new imageview(activity); 
    imageview.setlayoutparams(new layoutparams(10,10));
    imageview.setscaletype(scaletype.fit_xy);
    
    imageviews[index] = imageview;
     
    if (index == 0) { 
      //默认选中第一张图片
      imageviews[index].setbackgroundresource(r.drawable.dot_selected); 
    } else { 
      imageviews[index].setbackgroundresource(r.drawable.dot_none); 
    } 
     
    return imageviews[index];
  }

getlinearlayout()方法则是为圆点图片添加相应的linearlayout布局,以便设置圆点图片之间的距离,代码如下:

/**
   * 获取linearlayout
   * @param view
   * @param width
   * @param height
   * @return
   */
  public view getlinearlayout(view view,int width,int height){
    linearlayout linerlayout = new linearlayout(activity);
    linearlayout.layoutparams linerlayoutparames = new linearlayout.layoutparams(
        width, 
        height,
        1);
    // 这里最好也自定义设置,有兴趣的自己设置。
    linerlayout.setpadding(10, 0, 10, 0);
    linerlayout.addview(view, linerlayoutparames);
    
    return linerlayout;
  }

getcircleimagelayout()和getlinearlayout()方法在newstopic.java中for循环的结构中结合代码如下:

imagecircleviews[i] = slidelayout.getcircleimagelayout(i);
imagecircleview.addview(slidelayout.getlinearlayout(imagecircleviews[i], 10, 10));

这两个方法结合使用便能优美地实现其圆点图片的布局。

以上是关于newsxmlparser和slideimagelayout两个类的介绍,下面让我们再回到topicnews类中继续介绍相关知识。在topicnews中进行对象初始化(initeviews()方法)以后,还需要设置viewpager对象中的数据适配器和监听事件。viewpager中数据适配器的代码如下:

// 滑动图片数据适配器
  private class slideimageadapter extends pageradapter { 
    @override 
    public int getcount() { 
      return imagepageviews.size(); 
    } 
 
    @override 
    public boolean isviewfromobject(view arg0, object arg1) { 
      return arg0 == arg1; 
    } 
 
    @override 
    public int getitemposition(object object) { 
      // todo auto-generated method stub 
      return super.getitemposition(object); 
    } 
 
    @override 
    public void destroyitem(view arg0, int arg1, object arg2) { 
      // todo auto-generated method stub 
      ((viewpager) arg0).removeview(imagepageviews.get(arg1)); 
    } 
 
    @override 
    public object instantiateitem(view arg0, int arg1) { 
      // todo auto-generated method stub 
      ((viewpager) arg0).addview(imagepageviews.get(arg1));
      
      return imagepageviews.get(arg1); 
    } 
 
    @override 
    public void restorestate(parcelable arg0, classloader arg1) { 
      // todo auto-generated method stub 
 
    } 
 
    @override 
    public parcelable savestate() { 
      // todo auto-generated method stub 
      return null; 
    } 
 
    @override 
    public void startupdate(view arg0) { 
      // todo auto-generated method stub 
 
    } 
 
    @override 
    public void finishupdate(view arg0) { 
      // todo auto-generated method stub 
 
    } 
  }

而viewpager的事件监听器代码如下:

// 滑动页面更改事件监听器
  private class imagepagechangelistener implements onpagechangelistener {
    @override 
    public void onpagescrollstatechanged(int arg0) { 
      // todo auto-generated method stub 
 
    } 
 
    @override 
    public void onpagescrolled(int arg0, float arg1, int arg2) { 
      // todo auto-generated method stub 
 
    } 
 
    @override 
    public void onpageselected(int index) { 
      pageindex = index;
      slidelayout.setpageindex(index);
      tvslidetitle.settext(parser.getslidetitles()[index]);
      
      for (int i = 0; i < imagecircleviews.length; i++) { 
        imagecircleviews[index].setbackgroundresource(r.drawable.dot_selected);
        
        if (index != i) { 
          imagecircleviews[i].setbackgroundresource(r.drawable.dot_none); 
        } 
      }
    } 
  }

事件监听器中主要在回调函数onpageselected(int index)中变换标题和圆点图片。

由于滑动区域下方的内容是不变的,也就是不滑动的,正如在我在上面提到的,内容可能会超出屏幕的范围,所以我们需要使用scrollview以便内容过多的时候显示滚动条。可能一部分朋友会想到,要显示滚动条我也知道使用scrollview。我想在这里说的是,这里即有viewpager控件,也有scrollview,如果两个view单独使用不会有什么问题。然而不幸的是,两个一结合使用就出现了问题。什么问题呢?就是在滑动图片时出现反弹的现象,就是在滑动时很难滑动,我滑动时感觉很吃力,而且图片就是滑动不过去,这个就是两个view之间的冲突,因为两个view都是滑动的view,都会计算相应的位置和判断相应的距离。

我们如何来解决这个冲突呢?这里我们需要重写scrollview的onintercepttouchevent()回调函数。需要在程序里新加一个scrollviewextend类并继承自scrollview,下面是其代码:

package com.image.indicator.control;

import android.content.context;
import android.util.attributeset;
import android.view.motionevent;
import android.widget.scrollview;

/**
 * 能够兼容viewpager的scrollview
 * @description: 解决了viewpager在scrollview中的滑动反弹问题

 * @file: scrollviewextend.java

 * @package com.image.indicator.control

 * @author hanyonglu

 * @date 2012-6-18 下午01:34:50

 * @version v1.0
 */
public class scrollviewextend extends scrollview {
  // 滑动距离及坐标
  private float xdistance, ydistance, xlast, ylast;

  public scrollviewextend(context context, attributeset attrs) {
    super(context, attrs);
  }

  @override
  public boolean onintercepttouchevent(motionevent ev) {
    switch (ev.getaction()) {
      case motionevent.action_down:
        xdistance = ydistance = 0f;
        xlast = ev.getx();
        ylast = ev.gety();
        break;
      case motionevent.action_move:
        final float curx = ev.getx();
        final float cury = ev.gety();
        
        xdistance += math.abs(curx - xlast);
        ydistance += math.abs(cury - ylast);
        xlast = curx;
        ylast = cury;
        
        if(xdistance > ydistance){
          return false;
        } 
    }

    return super.onintercepttouchevent(ev);
  }
}

然后在我们的布局代码中添加这个扩展的view,如下代码:

<com.image.indicator.control.scrollviewextend
    android:layout_width="match_parent"
    android:layout_height="fill_parent">

    ……

</com.image.indicator.control.scrollviewextend>

以上的操作便可解决viewpager和scrollview之间冲突问题,这样便可使用滚动条顺利显示下方不变的内容。在这里再次给大家说明一下,由于本示例只是个演示示例,所以在滑动图片的下方,我只是用了一张图片固定地显示头条activity的下方。当然有需要的朋友,可以将其进行改造,将滑动图片的下方区域添加个listview等view之类的以显示相应要求的信息。

一些朋友可能会注意到,在滑动图片区域的下方有一段透明的效果,如下图所示:

Android实现局部图片滑动指引效果示例

这个实现也不难,只是在相应的布局代码中添加background属性即可,如下:

android:background="#55000000"

当然,透明度的设置有个范围,有兴趣的朋友到网上查找一下,这里不再详述。

本示例除了实现android局部图片滑动指引效果以外,还实现了上方导航菜单切换的效果,关于这个效果并不稀奇,因为网上有一些人已经实现该功能。不过在这里,我跟他们做不太一样的是,点击上方的新闻分类时灵敏度比较好,也就是说点中的概率比较大。因为上方的新闻分类文字比较小,要想点中有时不是件容易的事。下面简要说一下其实现过程及相应的代码。

由于要在点击新闻类别时背景图片需要动画效果,所以我添加了一个类:imageanimatioin,用于处理图片移动时动画效果。其

代码如下:

/**
   * 设置图像移动动画效果
   * @param v
   * @param startx
   * @param tox
   * @param starty
   * @param toy
   */
  public static void setimageslide(view v, int startx, int tox, int starty, int toy) {
    translateanimation anim = new translateanimation(startx, tox, starty, toy);
    anim.setduration(100);
    anim.setfillafter(true);
    v.startanimation(anim);
  }

下面展示一下点击新闻类别时的事件监听器中的代码,因为在这个过程中需要计算移动图片的位置和切换下面的主体内容,如下:

 // 新闻分类事件监听器
  private class itemonclicklistener implements onclicklistener{
    @override
    public void onclick(view v) {
      // todo auto-generated method stub
      itemwidth = findviewbyid(r.id.layout).getwidth();
      
      switch (v.getid()) {
      case r.id.tv_title_news:
        imageanimatioin.setimageslide(tvselecteditem, startx, 0, 0, 0);
        startx = 0;
        tvselecteditem.settext(r.string.title_news_category_tops);
        
        // 显示头条信息
        intent.setclass(mainactivity.this, topicnews.class);
        vnewsmain = getlocalactivitymanager().startactivity(
            "topicnews", intent).getdecorview();
        break;
      case r.id.tv_title_info:
        imageanimatioin.setimageslide(tvselecteditem, startx, itemwidth, 0, 0);
        startx = itemwidth;
        tvselecteditem.settext(r.string.title_news_category_info);
        
        // 显示资讯信息
        intent.setclass(mainactivity.this, infonews.class);
        vnewsmain = getlocalactivitymanager().startactivity(
            "infonews", intent).getdecorview();
        break;
      case r.id.tv_title_blog:
        imageanimatioin.setimageslide(tvselecteditem, startx, itemwidth * 2, 0, 0);
        startx = itemwidth * 2;
        tvselecteditem.settext(r.string.title_news_category_blog);
        
        // 显示博客信息
        intent.setclass(mainactivity.this, blognews.class);
        vnewsmain = getlocalactivitymanager().startactivity(
            "blognews", intent).getdecorview();
        break;
      case r.id.tv_title_magazine:
        imageanimatioin.setimageslide(tvselecteditem, startx, itemwidth * 3, 0, 0);
        startx = itemwidth * 3;
        tvselecteditem.settext(r.string.title_news_category_magazine);
        
        // 显示杂志信息
        intent.setclass(mainactivity.this, magazinenews.class);
        vnewsmain = getlocalactivitymanager().startactivity(
            "magazinenews", intent).getdecorview();
        break;
      case r.id.tv_title_domain:
        imageanimatioin.setimageslide(tvselecteditem, startx, itemwidth * 4, 0, 0);
        startx = itemwidth * 4;
        tvselecteditem.settext(r.string.title_news_category_domain);
        // 显示业界信息
        intent.setclass(mainactivity.this, domainnews.class);
        vnewsmain = getlocalactivitymanager().startactivity(
            "domainnews", intent).getdecorview();
        break;
      case r.id.tv_title_more:
        imageanimatioin.setimageslide(tvselecteditem, startx, itemwidth * 5, 0, 0);
        startx = itemwidth * 5;
        tvselecteditem.settext(r.string.title_news_category_more);
        
        // 显示更多信息
        intent.setclass(mainactivity.this, morenews.class);
        vnewsmain = getlocalactivitymanager().startactivity(
            "morenews", intent).getdecorview();
        break;
      default:
        break;
      }
      
      // 更换layout中的新闻主体
      rlnewsmain.removeallviews();
      rlnewsmain.addview(vnewsmain, params);
    }
  }

这里设置时是在一个主框架中变换新闻类别的布局,而不是直接显示,所以需要注意下。另外,在点击除头条之外的新闻类别时,下方展示的也只是个图片而已,有需要的朋友将其进行改造,这点不再多说。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。