Android中實現局部的圖片滑動指引效果

本文轉自:http://www.cnblogs.com/hanyonglu/archive/2012/06/19/2555113.html,以下內容均爲該作者所寫


其實關於Android滑動指引效果,我曾經發布過一篇文章,裏面實現的整個頁面的指引滑動,有興趣的朋友可以查看:http://www.cnblogs.com/hanyonglu/archive/2012/04/07/2435589.html

 

  另外關於Android中的局部滑動,我也曾發佈過一篇文章,是關於實現導航菜單的滑動,有興趣的朋友可以查看:http://www.cnblogs.com/hanyonglu/archive/2012/04/21/2462311.html

 

  今天發佈本文的原因是應一個網友要求,就是實現局部的圖片滑動指引效果。這種效果一般是在新聞客戶端上比較常見,其功能是:

  1、頂部單張圖片左右拖拉滑動;

  2、帶指引;

  3、僅滑動頂部單張圖片,不滑動頁面,下面的圖文內容不動;

  4、類似於新聞客戶端的功能

 

  爲了大家能更好的理解,我們先來看下要實現的效果圖:

   

    

    

 

  以上便是實現的效果圖,其實實現原理也並不難,我們只需要將android-support-v4.jar包中ViewPager控件設置成局部就可以,只是處理界面時稍微有點麻煩,不過看完本篇之後,大家以後使用時直接調用就行。也希望本篇能夠對大家有所幫助。

  好了,下面讓我們開始我們的實現過程,主要給大家介紹一下實現步驟和一些核心代碼。首先我們需要將android-support-v4.jar添加到工程當中,然後讓我們看一下程序結構:

 

   

 

  我先簡要介紹其實現原理:

  在佈局頁面中將設置成局部,限制其高度,然後爲滑動的圖片集合生成佈局界面,並在代碼中設置相應的數據適配器和監聽事件。在切換事件監聽器中更改相應的圓點圖片和顯示標題,由於滑動圖片下方的界面不需要改變內容,所以很很容易內容超過屏幕,所以需要設置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之類的以顯示相應要求的信息。

 

  一些朋友可能會注意到,在滑動圖片區域的下方有一段透明的效果,如下圖所示:

 

  

 

  這個實現也不難,只是在相應的佈局代碼中添加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);
        }
    }
複製代碼
發佈了3 篇原創文章 · 獲贊 4 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章