├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.core.resources.prefs └── org.eclipse.jdt.core.prefs ├── AndroidManifest.xml ├── README.md ├── ic_launcher-web.png ├── libs ├── android-support-v4.jar └── universal-image-loader-1.9.3.jar ├── proguard-project.txt ├── project.properties ├── res ├── anim │ ├── in_bottomtop.xml │ ├── in_topbottom.xml │ ├── out_bottomtop.xml │ └── out_topbottom.xml ├── drawable-hdpi │ ├── ic_launcher.png │ └── meinv.jpg ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ ├── ic_launcher.png │ └── notice_icon.png ├── drawable-xxhdpi │ └── ic_launcher.png ├── drawable │ └── progress_bar_states.xml ├── layout │ ├── activity_baseweb.xml │ ├── activity_main.xml │ ├── layout_notice.xml │ └── notice_item.xml ├── values-w820dp │ └── dimens.xml └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── src └── com └── finddreams ├── adbanner ├── BaseWebActivity.java ├── ImagePagerAdapter.java ├── MainActivity.java └── ProgressWebView.java └── bannerview ├── CircleFlowIndicator.java ├── FlowIndicator.java └── ViewFlow.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ADBanner 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/=UTF-8 3 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 3 | org.eclipse.jdt.core.compiler.compliance=1.6 4 | org.eclipse.jdt.core.compiler.source=1.6 5 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 13 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 关注finddreams,一起分享,一起进步: http://blog.csdn.net/finddreams/article/details/44619589 2 | 3 | 今天给大家带来一点干货,就是横向循环滚动的广告条。 4 | 有点类似淘宝的banner广告位,可以手势滑动,也会依据固定时间间隔自动滚动,滑到尽头时会一直循环。过渡非常的平滑。从网络中获取图片,并缓存到SD卡当中,做为没有网络的时候可以显示,提高用户的体验,点击每个广告条会进入相应的url界面,封装的好,使用起来非常的方便。 5 | 下面就让我们来看看运行的效果图,看看是不是你要找的: 6 | 7 | ![这里写图片描述](http://img.blog.csdn.net/20150325110434241) 8 | 9 |

10 | 记得之前为了做这样的效果,从网络上找了很多的源码。结果发现有很多的源码的效果要么是滑到尽头到第一个广告时,会从第二,三个广告中穿过,过渡非常的不平滑。要么就是封装的不够好,平滑效果是实现了,但却不能点击,或者说不能从网络上加载图片。 11 | 这两种情况都是不符合我们商业项目开发的。因为广告条会变,所以必须从后台获取图片的url地址,以及对应的广告信息的Url。 12 | 那今天给大家带来的干货就是为了解决这些问题,还你一个完美的广告条的实现。 13 | 我们都知道,广告条的效果一般的做法就是使用ViewPager加上一个定时器Time,TimerTask或者是handler来定时的滚动广告图片,同时控制广告的指示小点点的选中与未选中时显示的状态图片。 14 |

15 | 16 |   今天分享的广告条的做法有点不一样,但是效果确相比ViewPager更加的有趣。我们使用的是ViewFlow。 17 | **1.首先自定义一个ViewFlow类:** 18 | 因为这个类的代码量比较大,出于篇幅考虑,代码我就不贴了,见最下面的下载链接,下载源码可以自己去研究一下,具体的实现原理。 19 | 20 | **2.然后定义一个CircleFlowIndicator类** 21 | 这个类是来控制广告条中小圆点的滚动,从效果图中我们可以看到,三个小圆点的滚动也是非常的平滑的移动过去,让人感觉很流畅,比世面上很多的App中实现的那种广告小圆点的效果也要好很多。 22 | 具体代码依然见源码; 23 | 24 | **3.接下来我们就在布局文件中开始使用了** 25 | ``` 26 | 32 | 33 | 38 | 39 | 43 | 44 | 51 | 52 | 64 | 65 | 66 | 67 | 68 | ``` 69 | 70 | **4.然后我们就可以在Activity中调用了,具体的代码是:** 71 | ``` 72 | private void initView() { 73 | mViewFlow = (ViewFlow) findViewById(R.id.viewflow); 74 | mFlowIndicator = (CircleFlowIndicator) findViewById(R.id.viewflowindic); 75 | } 76 | 77 | private void initBanner(ArrayList imageUrlList) { 78 | 79 | mViewFlow.setAdapter(new ImagePagerAdapter(this, imageUrlList, 80 | linkUrlArray, titleList).setInfiniteLoop(true)); 81 | mViewFlow.setmSideBuffer(imageUrlList.size()); // 实际图片张数, 82 | mViewFlow.setFlowIndicator(mFlowIndicator); 83 | mViewFlow.setTimeSpan(4500); 84 | mViewFlow.setSelection(imageUrlList.size() * 1000); // 设置初始位置 85 | mViewFlow.startAutoFlowTimer(); // 启动自动播放 86 | } 87 | } 88 | ``` 89 | **5.有一个很关键的就是ImagePagerAdapter这个适配器,因为加载网络图片是在这个类里实现的,还有广告条的点击,进入一个Web界面的实现。在这里加载网络图片我们使用了一个很火的开源项目,UniversalImageLoader(异步加载网络图片) ,相信大家也并不陌生了。** 90 | ImagePagerAdapter.class 类: 91 | 92 | 93 | **6.点击广告条进入一个带进度条的WebView的Activity,这个效果我之前的博客中就有介绍过,详情可见:[仿微信中加载网页时带线行进度条的WebView的实现 ](http://blog.csdn.net/finddreams/article/details/44172)** 94 | 95 | 到这里就可以实现完美的广告条了,哦,对了因为是加载网络图片,所以加的要加上访问网络权限哦,不然就显示不了网络图片,还有因为用到了UniversalImageLoader把图片离线缓存到SD卡当中,所以也是加权限的。 96 | ``` 97 | 98 | 99 | ``` 100 | 终于实现了循环滚动,平滑过渡的广告条效果,真心让人松了一口气。为了不让初学者继续走前人的崎岖路,快速的进步。本着分享开源的精神,把源码发布出来,供大家学习,记住 分享开源会让你学到更多! 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/finddreams/ADBannerUI/5d22e8d4f6f2046bb005e443a7bb7dd96d3082a7/ic_launcher-web.png -------------------------------------------------------------------------------- /libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/finddreams/ADBannerUI/5d22e8d4f6f2046bb005e443a7bb7dd96d3082a7/libs/android-support-v4.jar -------------------------------------------------------------------------------- /libs/universal-image-loader-1.9.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/finddreams/ADBannerUI/5d22e8d4f6f2046bb005e443a7bb7dd96d3082a7/libs/universal-image-loader-1.9.3.jar -------------------------------------------------------------------------------- /proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-16 15 | -------------------------------------------------------------------------------- /res/anim/in_bottomtop.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /res/anim/in_topbottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /res/anim/out_bottomtop.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /res/anim/out_topbottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/finddreams/ADBannerUI/5d22e8d4f6f2046bb005e443a7bb7dd96d3082a7/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-hdpi/meinv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/finddreams/ADBannerUI/5d22e8d4f6f2046bb005e443a7bb7dd96d3082a7/res/drawable-hdpi/meinv.jpg -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/finddreams/ADBannerUI/5d22e8d4f6f2046bb005e443a7bb7dd96d3082a7/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/finddreams/ADBannerUI/5d22e8d4f6f2046bb005e443a7bb7dd96d3082a7/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/notice_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/finddreams/ADBannerUI/5d22e8d4f6f2046bb005e443a7bb7dd96d3082a7/res/drawable-xhdpi/notice_icon.png -------------------------------------------------------------------------------- /res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/finddreams/ADBannerUI/5d22e8d4f6f2046bb005e443a7bb7dd96d3082a7/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable/progress_bar_states.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /res/layout/activity_baseweb.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 18 | 19 | 26 | 27 | 39 | 40 | 41 | 47 | 48 | -------------------------------------------------------------------------------- /res/layout/layout_notice.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 22 | 23 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /res/layout/notice_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 64dp 9 | 10 | 11 | -------------------------------------------------------------------------------- /res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #00000000 4 | #fff0f0f0 5 | 6 | -------------------------------------------------------------------------------- /res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 16dp 6 | 12.0dip 7 | 15.0dip 8 | 8dip 9 | 10 | 11 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ADBanner 5 | Hello world! 6 | Settings 7 | 8 | 9 | -------------------------------------------------------------------------------- /res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | -------------------------------------------------------------------------------- /src/com/finddreams/adbanner/BaseWebActivity.java: -------------------------------------------------------------------------------- 1 | package com.finddreams.adbanner; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.widget.ProgressBar; 7 | 8 | 9 | 10 | /** 11 | * @Description:WebView界面,带自定义进度条显示 12 | * @author http://blog.csdn.net/finddreams 13 | */ 14 | public class BaseWebActivity extends Activity { 15 | 16 | protected ProgressWebView mWebView; 17 | private ProgressBar web_progressbar; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_baseweb); 23 | 24 | mWebView = (ProgressWebView) findViewById(R.id.baseweb_webview); 25 | mWebView.getSettings().setJavaScriptEnabled(true); 26 | initData(); 27 | } 28 | 29 | protected void initData() { 30 | Intent intent = getIntent(); 31 | Bundle bundle = intent.getExtras(); 32 | String url = bundle.getString("url"); 33 | 34 | // if(!TextUtils.isEmpty(url)&&TextUtils.isEmpty(title)){ 35 | mWebView.loadUrl(url); 36 | 37 | // } 38 | 39 | } 40 | 41 | @Override 42 | protected void onDestroy() { 43 | // TODO Auto-generated method stub 44 | super.onDestroy(); 45 | mWebView = null; 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/com/finddreams/adbanner/ImagePagerAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 trinea.cn All right reserved. This software is the confidential and proprietary information of 3 | * trinea.cn ("Confidential Information"). You shall not disclose such Confidential Information and shall use it only in 4 | * accordance with the terms of the license agreement you entered into with trinea.cn. 5 | */ 6 | package com.finddreams.adbanner; 7 | 8 | import java.util.List; 9 | 10 | import android.content.Context; 11 | import android.content.Intent; 12 | import android.graphics.Bitmap; 13 | import android.os.Bundle; 14 | import android.text.TextUtils; 15 | import android.view.View; 16 | import android.view.View.OnClickListener; 17 | import android.view.ViewGroup; 18 | import android.widget.BaseAdapter; 19 | import android.widget.ImageView; 20 | import android.widget.Toast; 21 | 22 | import com.nostra13.universalimageloader.core.DisplayImageOptions; 23 | import com.nostra13.universalimageloader.core.ImageLoader; 24 | import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; 25 | 26 | /** 27 | * @Description: 图片适配器 28 | * @author http://blog.csdn.net/finddreams 29 | */ 30 | public class ImagePagerAdapter extends BaseAdapter { 31 | 32 | private Context context; 33 | private List imageIdList; 34 | private List linkUrlArray; 35 | private List urlTitlesList; 36 | private int size; 37 | private boolean isInfiniteLoop; 38 | private ImageLoader imageLoader; 39 | private DisplayImageOptions options; 40 | 41 | public ImagePagerAdapter(Context context, List imageIdList, 42 | List urllist, List urlTitlesList) { 43 | this.context = context; 44 | this.imageIdList = imageIdList; 45 | if (imageIdList != null) { 46 | this.size = imageIdList.size(); 47 | } 48 | this.linkUrlArray = urllist; 49 | this.urlTitlesList = urlTitlesList; 50 | isInfiniteLoop = false; 51 | // 初始化imageLoader 否则会报错 52 | imageLoader = ImageLoader.getInstance(); 53 | imageLoader.init(ImageLoaderConfiguration.createDefault(context)); 54 | options = new DisplayImageOptions.Builder() 55 | .showStubImage(R.drawable.ic_launcher) // 设置图片下载期间显示的图片 56 | .showImageForEmptyUri(R.drawable.meinv) // 设置图片Uri为空或是错误的时候显示的图片 57 | .showImageOnFail(R.drawable.meinv) // 设置图片加载或解码过程中发生错误显示的图片 58 | .cacheInMemory(true) // 设置下载的图片是否缓存在内存中 59 | .cacheOnDisc(true) // 设置下载的图片是否缓存在SD卡中 60 | .build(); 61 | 62 | } 63 | 64 | @Override 65 | public int getCount() { 66 | // Infinite loop 67 | return isInfiniteLoop ? Integer.MAX_VALUE : imageIdList.size(); 68 | } 69 | 70 | /** 71 | * get really position 72 | * 73 | * @param position 74 | * @return 75 | */ 76 | private int getPosition(int position) { 77 | return isInfiniteLoop ? position % size : position; 78 | } 79 | 80 | @Override 81 | public View getView(final int position, View view, ViewGroup container) { 82 | final ViewHolder holder; 83 | if (view == null) { 84 | holder = new ViewHolder(); 85 | view = holder.imageView = new ImageView(context); 86 | holder.imageView 87 | .setLayoutParams(new ViewGroup.LayoutParams(-1, -1)); 88 | holder.imageView.setScaleType(ImageView.ScaleType.FIT_XY); 89 | view.setTag(holder); 90 | } else { 91 | holder = (ViewHolder) view.getTag(); 92 | } 93 | 94 | imageLoader.displayImage( 95 | (String) this.imageIdList.get(getPosition(position)), 96 | holder.imageView, options); 97 | 98 | holder.imageView.setOnClickListener(new OnClickListener() { 99 | 100 | @Override 101 | public void onClick(View arg0) { 102 | String url = linkUrlArray.get(ImagePagerAdapter.this 103 | .getPosition(position)); 104 | String title = urlTitlesList.get(ImagePagerAdapter.this 105 | .getPosition(position)); 106 | /* 107 | * if (TextUtils.isEmpty(url)) { 108 | * holder.imageView.setEnabled(false); return; } 109 | */ 110 | Bundle bundle = new Bundle(); 111 | 112 | bundle.putString("url", url); 113 | bundle.putString("title", title); 114 | Intent intent = new Intent(context, BaseWebActivity.class); 115 | intent.putExtras(bundle); 116 | 117 | context.startActivity(intent); 118 | Toast.makeText(context, "点击了第" + getPosition(position) + "美女", 119 | 0).show(); 120 | 121 | } 122 | }); 123 | 124 | return view; 125 | } 126 | 127 | private static class ViewHolder { 128 | 129 | ImageView imageView; 130 | } 131 | 132 | /** 133 | * @return the isInfiniteLoop 134 | */ 135 | public boolean isInfiniteLoop() { 136 | return isInfiniteLoop; 137 | } 138 | 139 | /** 140 | * @param isInfiniteLoop 141 | * the isInfiniteLoop to set 142 | */ 143 | public ImagePagerAdapter setInfiniteLoop(boolean isInfiniteLoop) { 144 | this.isInfiniteLoop = isInfiniteLoop; 145 | return this; 146 | } 147 | 148 | @Override 149 | public Object getItem(int arg0) { 150 | // TODO Auto-generated method stub 151 | return arg0; 152 | } 153 | 154 | @Override 155 | public long getItemId(int arg0) { 156 | // TODO Auto-generated method stub 157 | return arg0; 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /src/com/finddreams/adbanner/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.finddreams.adbanner; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Timer; 5 | import java.util.TimerTask; 6 | 7 | import android.app.Activity; 8 | import android.content.Intent; 9 | import android.os.Bundle; 10 | import android.util.Log; 11 | import android.view.View; 12 | import android.view.View.OnClickListener; 13 | import android.widget.FrameLayout; 14 | import android.widget.LinearLayout; 15 | import android.widget.TextView; 16 | import android.widget.ViewFlipper; 17 | 18 | import com.finddreams.bannerview.CircleFlowIndicator; 19 | import com.finddreams.bannerview.ViewFlow; 20 | 21 | /** 22 | * @Description:显示广告条的主页 23 | * @author http://blog.csdn.net/finddreams 24 | */ 25 | public class MainActivity extends Activity { 26 | 27 | private ViewFlow mViewFlow; 28 | private CircleFlowIndicator mFlowIndicator; 29 | private ArrayList imageUrlList = new ArrayList(); 30 | ArrayList linkUrlArray= new ArrayList(); 31 | ArrayList titleList= new ArrayList(); 32 | private LinearLayout notice_parent_ll; 33 | private LinearLayout notice_ll; 34 | private ViewFlipper notice_vf; 35 | private int mCurrPos; 36 | @Override 37 | protected void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | setContentView(R.layout.activity_main); 40 | initView(); 41 | imageUrlList 42 | .add("http://b.hiphotos.baidu.com/image/pic/item/d01373f082025aaf95bdf7e4f8edab64034f1a15.jpg"); 43 | imageUrlList 44 | .add("http://g.hiphotos.baidu.com/image/pic/item/6159252dd42a2834da6660c459b5c9ea14cebf39.jpg"); 45 | imageUrlList 46 | .add("http://d.hiphotos.baidu.com/image/pic/item/adaf2edda3cc7cd976427f6c3901213fb80e911c.jpg"); 47 | imageUrlList 48 | .add("http://g.hiphotos.baidu.com/image/pic/item/b3119313b07eca80131de3e6932397dda1448393.jpg"); 49 | 50 | linkUrlArray 51 | .add("http://blog.csdn.net/finddreams/article/details/44301359"); 52 | linkUrlArray 53 | .add("http://blog.csdn.net/finddreams/article/details/43486527"); 54 | linkUrlArray 55 | .add("http://blog.csdn.net/finddreams/article/details/44648121"); 56 | linkUrlArray 57 | .add("http://blog.csdn.net/finddreams/article/details/44619589"); 58 | titleList.add("常见Android进阶笔试题"); 59 | titleList.add("GridView之仿支付宝钱包首页"); 60 | titleList.add("仿手机QQ网络状态条的显示与消失 "); 61 | titleList.add("Android循环滚动广告条的完美实现 "); 62 | initBanner(imageUrlList); 63 | initRollNotice(); 64 | } 65 | 66 | private void initView() { 67 | mViewFlow = (ViewFlow) findViewById(R.id.viewflow); 68 | mFlowIndicator = (CircleFlowIndicator) findViewById(R.id.viewflowindic); 69 | 70 | } 71 | private void initRollNotice() { 72 | FrameLayout main_notice = (FrameLayout) findViewById(R.id.main_notice); 73 | notice_parent_ll = (LinearLayout) getLayoutInflater().inflate( 74 | R.layout.layout_notice, null); 75 | notice_ll = ((LinearLayout) this.notice_parent_ll 76 | .findViewById(R.id.homepage_notice_ll)); 77 | notice_vf = ((ViewFlipper) this.notice_parent_ll 78 | .findViewById(R.id.homepage_notice_vf)); 79 | main_notice.addView(notice_parent_ll); 80 | TimerTask task = new TimerTask() { 81 | @Override 82 | public void run() { 83 | runOnUiThread(new Runnable() { 84 | @Override 85 | public void run() { 86 | moveNext(); 87 | Log.d("Task", "下一个"); 88 | } 89 | }); 90 | 91 | } 92 | }; 93 | Timer timer = new Timer(); 94 | timer.schedule(task, 0, 4000); 95 | } 96 | 97 | private void moveNext() { 98 | setView(this.mCurrPos, this.mCurrPos + 1); 99 | this.notice_vf.setInAnimation(this, R.anim.in_bottomtop); 100 | this.notice_vf.setOutAnimation(this, R.anim.out_bottomtop); 101 | this.notice_vf.showNext(); 102 | } 103 | 104 | private void setView(int curr, int next) { 105 | 106 | View noticeView = getLayoutInflater().inflate(R.layout.notice_item, 107 | null); 108 | TextView notice_tv = (TextView) noticeView.findViewById(R.id.notice_tv); 109 | if ((curr < next) && (next > (titleList.size() - 1))) { 110 | next = 0; 111 | } else if ((curr > next) && (next < 0)) { 112 | next = titleList.size() - 1; 113 | } 114 | notice_tv.setText(titleList.get(next)); 115 | notice_tv.setOnClickListener(new OnClickListener() { 116 | 117 | @Override 118 | public void onClick(View arg0) { 119 | Bundle bundle = new Bundle(); 120 | bundle.putString("url", linkUrlArray.get(mCurrPos)); 121 | bundle.putString("title", titleList.get(mCurrPos)); 122 | Intent intent = new Intent(MainActivity.this, 123 | BaseWebActivity.class); 124 | intent.putExtras(bundle); 125 | startActivity(intent); 126 | } 127 | }); 128 | if (notice_vf.getChildCount() > 1) { 129 | notice_vf.removeViewAt(0); 130 | } 131 | notice_vf.addView(noticeView, notice_vf.getChildCount()); 132 | mCurrPos = next; 133 | 134 | } 135 | private void initBanner(ArrayList imageUrlList) { 136 | 137 | mViewFlow.setAdapter(new ImagePagerAdapter(this, imageUrlList, 138 | linkUrlArray, titleList).setInfiniteLoop(true)); 139 | mViewFlow.setmSideBuffer(imageUrlList.size()); // 实际图片张数, 140 | // 我的ImageAdapter实际图片张数为3 141 | 142 | mViewFlow.setFlowIndicator(mFlowIndicator); 143 | mViewFlow.setTimeSpan(4500); 144 | mViewFlow.setSelection(imageUrlList.size() * 1000); // 设置初始位置 145 | mViewFlow.startAutoFlowTimer(); // 启动自动播放 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/com/finddreams/adbanner/ProgressWebView.java: -------------------------------------------------------------------------------- 1 | package com.finddreams.adbanner; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.Drawable; 5 | import android.util.AttributeSet; 6 | import android.webkit.WebView; 7 | import android.widget.ProgressBar; 8 | 9 | 10 | /** 11 | * @Description: 带进度条的WebView 12 | * @author http://blog.csdn.net/finddreams 13 | */ 14 | @SuppressWarnings("deprecation") 15 | public class ProgressWebView extends WebView { 16 | 17 | private ProgressBar progressbar; 18 | 19 | public ProgressWebView(Context context, AttributeSet attrs) { 20 | super(context, attrs); 21 | progressbar = new ProgressBar(context, null, 22 | android.R.attr.progressBarStyleHorizontal); 23 | progressbar.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, 24 | 10, 0, 0)); 25 | 26 | Drawable drawable = context.getResources().getDrawable(R.drawable.progress_bar_states); 27 | progressbar.setProgressDrawable(drawable); 28 | addView(progressbar); 29 | // setWebViewClient(new WebViewClient(){}); 30 | setWebChromeClient(new WebChromeClient()); 31 | //是否支持缩放 32 | getSettings().setSupportZoom(true); 33 | getSettings().setBuiltInZoomControls(true); 34 | } 35 | 36 | public class WebChromeClient extends android.webkit.WebChromeClient { 37 | @Override 38 | public void onProgressChanged(WebView view, int newProgress) { 39 | if (newProgress == 100) { 40 | progressbar.setVisibility(GONE); 41 | } else { 42 | if (progressbar.getVisibility() == GONE) 43 | progressbar.setVisibility(VISIBLE); 44 | progressbar.setProgress(newProgress); 45 | } 46 | super.onProgressChanged(view, newProgress); 47 | } 48 | 49 | } 50 | 51 | @Override 52 | protected void onScrollChanged(int l, int t, int oldl, int oldt) { 53 | LayoutParams lp = (LayoutParams) progressbar.getLayoutParams(); 54 | lp.x = l; 55 | lp.y = t; 56 | progressbar.setLayoutParams(lp); 57 | super.onScrollChanged(l, t, oldl, oldt); 58 | } 59 | } -------------------------------------------------------------------------------- /src/com/finddreams/bannerview/CircleFlowIndicator.java: -------------------------------------------------------------------------------- 1 | 2 | package com.finddreams.bannerview; 3 | 4 | 5 | 6 | 7 | import com.finddreams.adbanner.R; 8 | 9 | import android.content.Context; 10 | import android.content.res.TypedArray; 11 | import android.graphics.Canvas; 12 | import android.graphics.Paint; 13 | import android.graphics.Paint.Style; 14 | import android.os.AsyncTask; 15 | import android.util.AttributeSet; 16 | import android.view.View; 17 | import android.view.animation.Animation; 18 | import android.view.animation.Animation.AnimationListener; 19 | import android.view.animation.AnimationUtils; 20 | 21 | /** 22 | * A FlowIndicator which draws circles (one for each view). 23 | *
24 | * Availables attributes are:
25 | *

28 | * 31 | * 34 | * 37 | * 40 | * 43 | * 44 | * @author http://blog.csdn.net/finddreams 45 | */ 46 | public class CircleFlowIndicator extends View implements FlowIndicator, 47 | AnimationListener { 48 | private static final int STYLE_STROKE = 0; 49 | private static final int STYLE_FILL = 1; 50 | 51 | private float radius = 4; 52 | private float circleSeparation = 2*radius+radius; 53 | private float activeRadius = 0.5f; 54 | private int fadeOutTime = 0; 55 | private final Paint mPaintInactive = new Paint(Paint.ANTI_ALIAS_FLAG); 56 | private final Paint mPaintActive = new Paint(Paint.ANTI_ALIAS_FLAG); 57 | private ViewFlow viewFlow; 58 | private int currentScroll = 0; 59 | private int flowWidth = 0; 60 | private FadeTimer timer; 61 | public AnimationListener animationListener = this; 62 | private Animation animation; 63 | private boolean mCentered = false; 64 | 65 | /** 66 | * Default constructor 67 | * 68 | * @param context 69 | */ 70 | public CircleFlowIndicator(Context context) { 71 | super(context); 72 | initColors(0xFFFFFFFF, 0xFFFFFFFF, STYLE_FILL, STYLE_STROKE); 73 | } 74 | 75 | /** 76 | * The contructor used with an inflater 77 | * 78 | * @param context 79 | * @param attrs 80 | */ 81 | public CircleFlowIndicator(Context context, AttributeSet attrs) { 82 | super(context, attrs); 83 | // Retrieve styles attributs 84 | TypedArray a = context.obtainStyledAttributes(attrs, 85 | R.styleable.CircleFlowIndicator); 86 | 87 | // Gets the inactive circle type, defaulting to "fill" 88 | int activeType = a.getInt(R.styleable.CircleFlowIndicator_activeType, 89 | STYLE_FILL); 90 | 91 | int activeDefaultColor = 0xFFFFFFFF; 92 | 93 | // Get a custom inactive color if there is one 94 | int activeColor = a 95 | .getColor(R.styleable.CircleFlowIndicator_activeColor, 96 | activeDefaultColor); 97 | 98 | // Gets the inactive circle type, defaulting to "stroke" 99 | int inactiveType = a.getInt( 100 | R.styleable.CircleFlowIndicator_inactiveType, STYLE_STROKE); 101 | 102 | int inactiveDefaultColor = 0x44FFFFFF; 103 | // Get a custom inactive color if there is one 104 | int inactiveColor = a.getColor( 105 | R.styleable.CircleFlowIndicator_inactiveColor, 106 | inactiveDefaultColor); 107 | 108 | // Retrieve the radius 109 | radius = a.getDimension(R.styleable.CircleFlowIndicator_radius, 4.0f); 110 | 111 | circleSeparation = a.getDimension(R.styleable.CircleFlowIndicator_circleSeparation, 2*radius+radius); 112 | activeRadius = a.getDimension(R.styleable.CircleFlowIndicator_activeRadius, 0.5f); 113 | // Retrieve the fade out time 114 | fadeOutTime = a.getInt(R.styleable.CircleFlowIndicator_fadeOut, 0); 115 | 116 | mCentered = a.getBoolean(R.styleable.CircleFlowIndicator_centered, false); 117 | 118 | initColors(activeColor, inactiveColor, activeType, inactiveType); 119 | } 120 | 121 | private void initColors(int activeColor, int inactiveColor, int activeType, 122 | int inactiveType) { 123 | // Select the paint type given the type attr 124 | switch (inactiveType) { 125 | case STYLE_FILL: 126 | mPaintInactive.setStyle(Style.FILL); 127 | break; 128 | default: 129 | mPaintInactive.setStyle(Style.STROKE); 130 | } 131 | mPaintInactive.setColor(inactiveColor); 132 | 133 | // Select the paint type given the type attr 134 | switch (activeType) { 135 | case STYLE_STROKE: 136 | mPaintActive.setStyle(Style.STROKE); 137 | break; 138 | default: 139 | mPaintActive.setStyle(Style.FILL); 140 | } 141 | mPaintActive.setColor(activeColor); 142 | } 143 | 144 | /* 145 | * (non-Javadoc) 146 | * 147 | * @see android.view.View#onDraw(android.graphics.Canvas) 148 | */ 149 | @Override 150 | protected void onDraw(Canvas canvas) { 151 | super.onDraw(canvas); 152 | int count = 3; 153 | if (viewFlow != null) { 154 | count = viewFlow.getViewsCount(); 155 | } 156 | 157 | //this is the amount the first circle should be offset to make the entire thing centered 158 | float centeringOffset = 0; 159 | 160 | int leftPadding = getPaddingLeft(); 161 | 162 | // Draw stroked circles 163 | for (int iLoop = 0; iLoop < count; iLoop++) { 164 | canvas.drawCircle(leftPadding + radius 165 | + (iLoop * circleSeparation) + centeringOffset, 166 | getPaddingTop() + radius, radius, mPaintInactive); 167 | } 168 | float cx = 0; 169 | if (flowWidth != 0) { 170 | // Draw the filled circle according to the current scroll 171 | cx = (currentScroll * circleSeparation) / flowWidth; 172 | } 173 | // The flow width has been upadated yet. Draw the default position 174 | canvas.drawCircle(leftPadding + radius + cx+centeringOffset, getPaddingTop() 175 | + radius, radius + activeRadius, mPaintActive); 176 | } 177 | 178 | /* 179 | * (non-Javadoc) 180 | * 181 | * @see 182 | * org.taptwo.android.widget.ViewFlow.ViewSwitchListener#onSwitched(android 183 | * .view.View, int) 184 | */ 185 | @Override 186 | public void onSwitched(View view, int position) { 187 | } 188 | 189 | /* 190 | * (non-Javadoc) 191 | * 192 | * @see 193 | * org.taptwo.android.widget.FlowIndicator#setViewFlow(org.taptwo.android 194 | * .widget.ViewFlow) 195 | */ 196 | @Override 197 | public void setViewFlow(ViewFlow view) { 198 | resetTimer(); 199 | viewFlow = view; 200 | flowWidth = viewFlow.getWidth(); 201 | invalidate(); 202 | } 203 | 204 | /* 205 | * (non-Javadoc) 206 | * 207 | * @see org.taptwo.android.widget.FlowIndicator#onScrolled(int, int, int, 208 | * int) 209 | */ 210 | @Override 211 | public void onScrolled(int h, int v, int oldh, int oldv) { 212 | setVisibility(View.VISIBLE); 213 | resetTimer(); 214 | flowWidth = viewFlow.getWidth(); 215 | if(viewFlow.getViewsCount()*flowWidth!=0){ 216 | currentScroll = h%(viewFlow.getViewsCount()*flowWidth); 217 | }else { 218 | currentScroll = h; 219 | } 220 | invalidate(); 221 | } 222 | 223 | /* 224 | * (non-Javadoc) 225 | * 226 | * @see android.view.View#onMeasure(int, int) 227 | */ 228 | @Override 229 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 230 | setMeasuredDimension(measureWidth(widthMeasureSpec), 231 | measureHeight(heightMeasureSpec)); 232 | } 233 | 234 | /** 235 | * Determines the width of this view 236 | * 237 | * @param measureSpec 238 | * A measureSpec packed into an int 239 | * @return The width of the view, honoring constraints from measureSpec 240 | */ 241 | private int measureWidth(int measureSpec) { 242 | int result = 0; 243 | int specMode = MeasureSpec.getMode(measureSpec); 244 | int specSize = MeasureSpec.getSize(measureSpec); 245 | 246 | // We were told how big to be 247 | if (specMode == MeasureSpec.EXACTLY) { 248 | result = specSize; 249 | } 250 | // Calculate the width according the views count 251 | else { 252 | int count = 3; 253 | if (viewFlow != null) { 254 | count = viewFlow.getViewsCount(); 255 | } 256 | float temp = circleSeparation - 2*radius; 257 | result = (int) (getPaddingLeft() + getPaddingRight() 258 | + (count * 2 * radius) + (count - 1) * temp + 1); 259 | // Respect AT_MOST value if that was what is called for by 260 | // measureSpec 261 | if (specMode == MeasureSpec.AT_MOST) { 262 | result = Math.min(result, specSize); 263 | } 264 | } 265 | return result; 266 | } 267 | 268 | /** 269 | * Determines the height of this view 270 | * 271 | * @param measureSpec 272 | * A measureSpec packed into an int 273 | * @return The height of the view, honoring constraints from measureSpec 274 | */ 275 | private int measureHeight(int measureSpec) { 276 | int result = 0; 277 | int specMode = MeasureSpec.getMode(measureSpec); 278 | int specSize = MeasureSpec.getSize(measureSpec); 279 | 280 | // We were told how big to be 281 | if (specMode == MeasureSpec.EXACTLY) { 282 | result = specSize; 283 | } 284 | // Measure the height 285 | else { 286 | result = (int) (2 * radius + getPaddingTop() + getPaddingBottom() + 1); 287 | // Respect AT_MOST value if that was what is called for by 288 | // measureSpec 289 | if (specMode == MeasureSpec.AT_MOST) { 290 | result = Math.min(result, specSize); 291 | } 292 | } 293 | return result; 294 | } 295 | 296 | /** 297 | * Sets the fill color 298 | * 299 | * @param color 300 | * ARGB value for the text 301 | */ 302 | public void setFillColor(int color) { 303 | mPaintActive.setColor(color); 304 | invalidate(); 305 | } 306 | 307 | /** 308 | * Sets the stroke color 309 | * 310 | * @param color 311 | * ARGB value for the text 312 | */ 313 | public void setStrokeColor(int color) { 314 | mPaintInactive.setColor(color); 315 | invalidate(); 316 | } 317 | 318 | /** 319 | * Resets the fade out timer to 0. Creating a new one if needed 320 | */ 321 | private void resetTimer() { 322 | // Only set the timer if we have a timeout of at least 1 millisecond 323 | if (fadeOutTime > 0) { 324 | // Check if we need to create a new timer 325 | if (timer == null || timer._run == false) { 326 | // Create and start a new timer 327 | timer = new FadeTimer(); 328 | timer.execute(); 329 | } else { 330 | // Reset the current tiemr to 0 331 | timer.resetTimer(); 332 | } 333 | } 334 | } 335 | 336 | /** 337 | * Counts from 0 to the fade out time and animates the view away when 338 | * reached 339 | */ 340 | private class FadeTimer extends AsyncTask { 341 | // The current count 342 | private int timer = 0; 343 | // If we are inside the timing loop 344 | private boolean _run = true; 345 | 346 | public void resetTimer() { 347 | timer = 0; 348 | } 349 | 350 | @Override 351 | protected Void doInBackground(Void... arg0) { 352 | while (_run) { 353 | try { 354 | // Wait for a millisecond 355 | Thread.sleep(1); 356 | // Increment the timer 357 | timer++; 358 | 359 | // Check if we've reached the fade out time 360 | if (timer == fadeOutTime) { 361 | // Stop running 362 | _run = false; 363 | } 364 | } catch (InterruptedException e) { 365 | // TODO Auto-generated catch block 366 | e.printStackTrace(); 367 | } 368 | } 369 | return null; 370 | } 371 | 372 | @Override 373 | protected void onPostExecute(Void result) { 374 | animation = AnimationUtils.loadAnimation(getContext(), 375 | android.R.anim.fade_out); 376 | animation.setAnimationListener(animationListener); 377 | startAnimation(animation); 378 | } 379 | } 380 | 381 | @Override 382 | public void onAnimationEnd(Animation animation) { 383 | setVisibility(View.GONE); 384 | } 385 | 386 | @Override 387 | public void onAnimationRepeat(Animation animation) { 388 | } 389 | 390 | @Override 391 | public void onAnimationStart(Animation animation) { 392 | } 393 | } -------------------------------------------------------------------------------- /src/com/finddreams/bannerview/FlowIndicator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Patrik �kerfeldt 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.finddreams.bannerview; 17 | 18 | import com.finddreams.bannerview.ViewFlow.ViewSwitchListener; 19 | 20 | /** 21 | * An interface which defines the contract between a ViewFlow and a 22 | * FlowIndicator.
23 | * A FlowIndicator is responsible to show an visual indicator on the total views 24 | * number and the current visible view.
25 | * @author http://blog.csdn.net/finddreams 26 | */ 27 | public interface FlowIndicator extends ViewSwitchListener { 28 | 29 | /** 30 | * Set the current ViewFlow. This method is called by the ViewFlow when the 31 | * FlowIndicator is attached to it. 32 | * 33 | * @param view 34 | */ 35 | public void setViewFlow(ViewFlow view); 36 | 37 | /** 38 | * The scroll position has been changed. A FlowIndicator may implement this 39 | * method to reflect the current position 40 | * 41 | * @param h 42 | * @param v 43 | * @param oldh 44 | * @param oldv 45 | */ 46 | public void onScrolled(int h, int v, int oldh, int oldv); 47 | } 48 | -------------------------------------------------------------------------------- /src/com/finddreams/bannerview/ViewFlow.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Patrik Åkerfeldt 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.finddreams.bannerview; 17 | 18 | import java.util.ArrayList; 19 | import java.util.LinkedList; 20 | 21 | import android.content.Context; 22 | import android.content.res.Configuration; 23 | import android.content.res.TypedArray; 24 | import android.database.DataSetObserver; 25 | import android.os.Handler; 26 | import android.os.Message; 27 | import android.util.AttributeSet; 28 | import android.view.MotionEvent; 29 | import android.view.VelocityTracker; 30 | import android.view.View; 31 | import android.view.ViewConfiguration; 32 | import android.view.ViewGroup; 33 | import android.view.ViewTreeObserver.OnGlobalLayoutListener; 34 | import android.widget.AbsListView; 35 | import android.widget.Adapter; 36 | import android.widget.AdapterView; 37 | import android.widget.Scroller; 38 | 39 | import com.finddreams.adbanner.R; 40 | 41 | /** 42 | * 43 | * A horizontally scrollable {@link ViewGroup} with items populated from an 44 | * {@link Adapter}. The ViewFlow uses a buffer to store loaded {@link View}s in. 45 | * The default size of the buffer is 3 elements on both sides of the currently 46 | * visible {@link View}, making up a total buffer size of 3 * 2 + 1 = 7. The 47 | * buffer size can be changed using the {@code sidebuffer} xml attribute. 48 | * 49 | * @author http://blog.csdn.net/finddreams 50 | */ 51 | public class ViewFlow extends AdapterView { 52 | 53 | private static final int SNAP_VELOCITY = 1000; 54 | private static final int INVALID_SCREEN = -1; 55 | private final static int TOUCH_STATE_REST = 0; 56 | private final static int TOUCH_STATE_SCROLLING = 1; 57 | 58 | private LinkedList mLoadedViews; 59 | private int mCurrentBufferIndex; 60 | private int mCurrentAdapterIndex; 61 | private int mSideBuffer = 2; 62 | private Scroller mScroller; 63 | private VelocityTracker mVelocityTracker; 64 | private int mTouchState = TOUCH_STATE_REST; 65 | private float mLastMotionX; 66 | private int mTouchSlop; 67 | private int mMaximumVelocity; 68 | private int mCurrentScreen; 69 | private int mNextScreen = INVALID_SCREEN; 70 | private boolean mFirstLayout = true; 71 | private ViewSwitchListener mViewSwitchListener; 72 | private Adapter mAdapter; 73 | private int mLastScrollDirection; 74 | private AdapterDataSetObserver mDataSetObserver; 75 | private FlowIndicator mIndicator; 76 | private int mLastOrientation = -1; 77 | private long timeSpan = 3000; 78 | private Handler handler; 79 | private OnGlobalLayoutListener orientationChangeListener = new OnGlobalLayoutListener() { 80 | 81 | @Override 82 | public void onGlobalLayout() { 83 | getViewTreeObserver().removeGlobalOnLayoutListener( 84 | orientationChangeListener); 85 | setSelection(mCurrentAdapterIndex); 86 | } 87 | }; 88 | 89 | /** 90 | * Receives call backs when a new {@link View} has been scrolled to. 91 | */ 92 | public static interface ViewSwitchListener { 93 | 94 | /** 95 | * This method is called when a new View has been scrolled to. 96 | * 97 | * @param view 98 | * the {@link View} currently in focus. 99 | * @param position 100 | * The position in the adapter of the {@link View} currently in focus. 101 | */ 102 | void onSwitched(View view, int position); 103 | 104 | } 105 | 106 | public ViewFlow(Context context) { 107 | super(context); 108 | mSideBuffer = 3; 109 | init(); 110 | } 111 | 112 | public ViewFlow(Context context, int sideBuffer) { 113 | super(context); 114 | mSideBuffer = sideBuffer; 115 | init(); 116 | } 117 | 118 | public ViewFlow(Context context, AttributeSet attrs) { 119 | super(context, attrs); 120 | TypedArray styledAttrs = context.obtainStyledAttributes(attrs, 121 | R.styleable.ViewFlow); 122 | mSideBuffer = styledAttrs.getInt(R.styleable.ViewFlow_sidebuffer, 3); 123 | init(); 124 | } 125 | 126 | private void init() { 127 | mLoadedViews = new LinkedList(); 128 | mScroller = new Scroller(getContext()); 129 | final ViewConfiguration configuration = ViewConfiguration 130 | .get(getContext()); 131 | mTouchSlop = configuration.getScaledTouchSlop(); 132 | mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 133 | } 134 | 135 | public void startAutoFlowTimer(){ 136 | handler = new Handler(){ 137 | @Override 138 | public void handleMessage(Message msg) { 139 | 140 | snapToScreen((mCurrentScreen+1)%getChildCount()); 141 | Message message = handler.obtainMessage(0); 142 | sendMessageDelayed(message, timeSpan); 143 | } 144 | }; 145 | 146 | Message message = handler.obtainMessage(0); 147 | handler.sendMessageDelayed(message, timeSpan); 148 | } 149 | public void stopAutoFlowTimer(){ 150 | if(handler!=null) 151 | handler.removeMessages(0); 152 | handler = null; 153 | } 154 | 155 | public void onConfigurationChanged(Configuration newConfig) { 156 | if (newConfig.orientation != mLastOrientation) { 157 | mLastOrientation = newConfig.orientation; 158 | getViewTreeObserver().addOnGlobalLayoutListener(orientationChangeListener); 159 | } 160 | } 161 | 162 | public int getViewsCount() { 163 | return mSideBuffer; 164 | } 165 | 166 | @Override 167 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 168 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 169 | 170 | final int width = MeasureSpec.getSize(widthMeasureSpec); 171 | final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 172 | if (widthMode != MeasureSpec.EXACTLY && !isInEditMode()) { 173 | throw new IllegalStateException( 174 | "ViewFlow can only be used in EXACTLY mode."); 175 | } 176 | 177 | final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 178 | if (heightMode != MeasureSpec.EXACTLY && !isInEditMode()) { 179 | throw new IllegalStateException( 180 | "ViewFlow can only be used in EXACTLY mode."); 181 | } 182 | 183 | // The children are given the same width and height as the workspace 184 | final int count = getChildCount(); 185 | for (int i = 0; i < count; i++) { 186 | getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); 187 | } 188 | 189 | if (mFirstLayout) { 190 | mScroller.startScroll(0, 0, mCurrentScreen * width, 0, 0); 191 | mFirstLayout = false; 192 | } 193 | } 194 | 195 | @Override 196 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 197 | int childLeft = 0; 198 | 199 | final int count = getChildCount(); 200 | for (int i = 0; i < count; i++) { 201 | final View child = getChildAt(i); 202 | if (child.getVisibility() != View.GONE) { 203 | final int childWidth = child.getMeasuredWidth(); 204 | child.layout(childLeft, 0, childLeft + childWidth, 205 | child.getMeasuredHeight()); 206 | childLeft += childWidth; 207 | } 208 | } 209 | } 210 | 211 | @Override 212 | public boolean onInterceptTouchEvent(MotionEvent ev) { 213 | if (getChildCount() == 0) 214 | return false; 215 | 216 | if (mVelocityTracker == null) { 217 | mVelocityTracker = VelocityTracker.obtain(); 218 | } 219 | mVelocityTracker.addMovement(ev); 220 | 221 | final int action = ev.getAction(); 222 | final float x = ev.getX(); 223 | 224 | switch (action) { 225 | case MotionEvent.ACTION_DOWN: 226 | /* 227 | * If being flinged and user touches, stop the fling. isFinished 228 | * will be false if being flinged. 229 | */ 230 | if (!mScroller.isFinished()) { 231 | mScroller.abortAnimation(); 232 | } 233 | 234 | // Remember where the motion event started 235 | mLastMotionX = x; 236 | 237 | mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST 238 | : TOUCH_STATE_SCROLLING; 239 | if(handler!=null) 240 | handler.removeMessages(0); 241 | break; 242 | 243 | case MotionEvent.ACTION_MOVE: 244 | final int xDiff = (int) Math.abs(x - mLastMotionX); 245 | 246 | boolean xMoved = xDiff > mTouchSlop; 247 | 248 | if (xMoved) { 249 | // Scroll if the user moved far enough along the X axis 250 | mTouchState = TOUCH_STATE_SCROLLING; 251 | } 252 | 253 | if (mTouchState == TOUCH_STATE_SCROLLING) { 254 | // Scroll to follow the motion event 255 | final int deltaX = (int) (mLastMotionX - x); 256 | mLastMotionX = x; 257 | 258 | final int scrollX = getScrollX(); 259 | if (deltaX < 0) { 260 | if (scrollX > 0) { 261 | scrollBy(Math.max(-scrollX, deltaX), 0); 262 | } 263 | } else if (deltaX > 0) { 264 | final int availableToScroll = getChildAt( 265 | getChildCount() - 1).getRight() 266 | - scrollX - getWidth(); 267 | if (availableToScroll > 0) { 268 | scrollBy(Math.min(availableToScroll, deltaX), 0); 269 | } 270 | } 271 | return true; 272 | } 273 | break; 274 | 275 | case MotionEvent.ACTION_UP: 276 | if (mTouchState == TOUCH_STATE_SCROLLING) { 277 | final VelocityTracker velocityTracker = mVelocityTracker; 278 | velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 279 | int velocityX = (int) velocityTracker.getXVelocity(); 280 | 281 | if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) { 282 | // Fling hard enough to move left 283 | snapToScreen(mCurrentScreen - 1); 284 | } else if (velocityX < -SNAP_VELOCITY 285 | && mCurrentScreen < getChildCount() - 1) { 286 | // Fling hard enough to move right 287 | snapToScreen(mCurrentScreen + 1); 288 | } else { 289 | snapToDestination(); 290 | } 291 | 292 | if (mVelocityTracker != null) { 293 | mVelocityTracker.recycle(); 294 | mVelocityTracker = null; 295 | } 296 | } 297 | 298 | mTouchState = TOUCH_STATE_REST; 299 | if(handler!=null){ 300 | Message message = handler.obtainMessage(0); 301 | handler.sendMessageDelayed(message, timeSpan); 302 | } 303 | break; 304 | case MotionEvent.ACTION_CANCEL: 305 | mTouchState = TOUCH_STATE_REST; 306 | } 307 | return false; 308 | } 309 | 310 | @Override 311 | public boolean onTouchEvent(MotionEvent ev) { 312 | if (getChildCount() == 0) 313 | return false; 314 | 315 | if (mVelocityTracker == null) { 316 | mVelocityTracker = VelocityTracker.obtain(); 317 | } 318 | mVelocityTracker.addMovement(ev); 319 | 320 | final int action = ev.getAction(); 321 | final float x = ev.getX(); 322 | 323 | switch (action) { 324 | case MotionEvent.ACTION_DOWN: 325 | /* 326 | * If being flinged and user touches, stop the fling. isFinished 327 | * will be false if being flinged. 328 | */ 329 | if (!mScroller.isFinished()) { 330 | mScroller.abortAnimation(); 331 | } 332 | 333 | // Remember where the motion event started 334 | mLastMotionX = x; 335 | 336 | mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST 337 | : TOUCH_STATE_SCROLLING; 338 | if(handler!=null) 339 | handler.removeMessages(0); 340 | break; 341 | 342 | case MotionEvent.ACTION_MOVE: 343 | final int xDiff = (int) Math.abs(x - mLastMotionX); 344 | 345 | boolean xMoved = xDiff > mTouchSlop; 346 | 347 | if (xMoved) { 348 | // Scroll if the user moved far enough along the X axis 349 | mTouchState = TOUCH_STATE_SCROLLING; 350 | } 351 | 352 | if (mTouchState == TOUCH_STATE_SCROLLING) { 353 | // Scroll to follow the motion event 354 | final int deltaX = (int) (mLastMotionX - x); 355 | mLastMotionX = x; 356 | 357 | final int scrollX = getScrollX(); 358 | if (deltaX < 0) { 359 | if (scrollX > 0) { 360 | scrollBy(Math.max(-scrollX, deltaX), 0); 361 | } 362 | } else if (deltaX > 0) { 363 | final int availableToScroll = getChildAt( 364 | getChildCount() - 1).getRight() 365 | - scrollX - getWidth(); 366 | if (availableToScroll > 0) { 367 | scrollBy(Math.min(availableToScroll, deltaX), 0); 368 | } 369 | } 370 | return true; 371 | } 372 | break; 373 | 374 | case MotionEvent.ACTION_UP: 375 | if (mTouchState == TOUCH_STATE_SCROLLING) { 376 | final VelocityTracker velocityTracker = mVelocityTracker; 377 | velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 378 | int velocityX = (int) velocityTracker.getXVelocity(); 379 | 380 | if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) { 381 | // Fling hard enough to move left 382 | snapToScreen(mCurrentScreen - 1); 383 | } else if (velocityX < -SNAP_VELOCITY 384 | && mCurrentScreen < getChildCount() - 1) { 385 | // Fling hard enough to move right 386 | snapToScreen(mCurrentScreen + 1); 387 | } 388 | // else if (velocityX < -SNAP_VELOCITY 389 | // && mCurrentScreen == getChildCount() - 1) { 390 | // snapToScreen(0); 391 | // } 392 | // else if (velocityX > SNAP_VELOCITY 393 | // && mCurrentScreen == 0) { 394 | // snapToScreen(getChildCount() - 1); 395 | // } 396 | else { 397 | snapToDestination(); 398 | } 399 | 400 | if (mVelocityTracker != null) { 401 | mVelocityTracker.recycle(); 402 | mVelocityTracker = null; 403 | } 404 | } 405 | 406 | mTouchState = TOUCH_STATE_REST; 407 | 408 | if(handler!=null){ 409 | Message message = handler.obtainMessage(0); 410 | handler.sendMessageDelayed(message, timeSpan); 411 | } 412 | break; 413 | case MotionEvent.ACTION_CANCEL: 414 | snapToDestination(); 415 | mTouchState = TOUCH_STATE_REST; 416 | } 417 | return true; 418 | } 419 | 420 | @Override 421 | protected void onScrollChanged(int h, int v, int oldh, int oldv) { 422 | super.onScrollChanged(h, v, oldh, oldv); 423 | if (mIndicator != null) { 424 | /* 425 | * The actual horizontal scroll origin does typically not match the 426 | * perceived one. Therefore, we need to calculate the perceived 427 | * horizontal scroll origin here, since we use a view buffer. 428 | */ 429 | int hPerceived = h + (mCurrentAdapterIndex - mCurrentBufferIndex) 430 | * getWidth(); 431 | mIndicator.onScrolled(hPerceived, v, oldh, oldv); 432 | } 433 | } 434 | 435 | private void snapToDestination() { 436 | final int screenWidth = getWidth(); 437 | final int whichScreen = (getScrollX() + (screenWidth / 2)) 438 | / screenWidth; 439 | 440 | snapToScreen(whichScreen); 441 | } 442 | 443 | private void snapToScreen(int whichScreen) { 444 | mLastScrollDirection = whichScreen - mCurrentScreen; 445 | if (!mScroller.isFinished()) 446 | return; 447 | 448 | whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); 449 | 450 | mNextScreen = whichScreen; 451 | 452 | final int newX = whichScreen * getWidth(); 453 | final int delta = newX - getScrollX(); 454 | mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2); 455 | invalidate(); 456 | } 457 | 458 | @Override 459 | public void computeScroll() { 460 | if (mScroller.computeScrollOffset()) { 461 | scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 462 | postInvalidate(); 463 | } else if (mNextScreen != INVALID_SCREEN) { 464 | mCurrentScreen = Math.max(0, 465 | Math.min(mNextScreen, getChildCount() - 1)); 466 | mNextScreen = INVALID_SCREEN; 467 | postViewSwitched(mLastScrollDirection); 468 | } 469 | } 470 | 471 | /** 472 | * Scroll to the {@link View} in the view buffer specified by the index. 473 | * 474 | * @param indexInBuffer 475 | * Index of the view in the view buffer. 476 | */ 477 | private void setVisibleView(int indexInBuffer, boolean uiThread) { 478 | mCurrentScreen = Math.max(0, 479 | Math.min(indexInBuffer, getChildCount() - 1)); 480 | int dx = (mCurrentScreen * getWidth()) - mScroller.getCurrX(); 481 | mScroller.startScroll(mScroller.getCurrX(), mScroller.getCurrY(), dx, 482 | 0, 0); 483 | if(dx == 0) 484 | onScrollChanged(mScroller.getCurrX() + dx, mScroller.getCurrY(), mScroller.getCurrX() + dx, mScroller.getCurrY()); 485 | if (uiThread) 486 | invalidate(); 487 | else 488 | postInvalidate(); 489 | } 490 | 491 | /** 492 | * Set the listener that will receive notifications every time the {code 493 | * ViewFlow} scrolls. 494 | * 495 | * @param l 496 | * the scroll listener 497 | */ 498 | public void setOnViewSwitchListener(ViewSwitchListener l) { 499 | mViewSwitchListener = l; 500 | } 501 | 502 | @Override 503 | public Adapter getAdapter() { 504 | return mAdapter; 505 | } 506 | 507 | @Override 508 | public void setAdapter(Adapter adapter) { 509 | setAdapter(adapter, 0); 510 | } 511 | 512 | public void setAdapter(Adapter adapter, int initialPosition) { 513 | if (mAdapter != null) { 514 | mAdapter.unregisterDataSetObserver(mDataSetObserver); 515 | } 516 | 517 | mAdapter = adapter; 518 | 519 | if (mAdapter != null) { 520 | mDataSetObserver = new AdapterDataSetObserver(); 521 | mAdapter.registerDataSetObserver(mDataSetObserver); 522 | 523 | } 524 | if (mAdapter == null || mAdapter.getCount() == 0) 525 | return; 526 | 527 | setSelection(initialPosition); 528 | } 529 | 530 | @Override 531 | public View getSelectedView() { 532 | return (mCurrentBufferIndex < mLoadedViews.size() ? mLoadedViews 533 | .get(mCurrentBufferIndex) : null); 534 | } 535 | 536 | @Override 537 | public int getSelectedItemPosition() { 538 | return mCurrentAdapterIndex; 539 | } 540 | 541 | /** 542 | * Set the FlowIndicator 543 | * 544 | * @param flowIndicator 545 | */ 546 | public void setFlowIndicator(FlowIndicator flowIndicator) { 547 | mIndicator = flowIndicator; 548 | mIndicator.setViewFlow(this); 549 | } 550 | 551 | @Override 552 | public void setSelection(int position) { 553 | mNextScreen = INVALID_SCREEN; 554 | mScroller.forceFinished(true); 555 | if (mAdapter == null) 556 | return; 557 | 558 | position = Math.max(position, 0); 559 | position = Math.min(position, mAdapter.getCount()-1); 560 | 561 | ArrayList recycleViews = new ArrayList(); 562 | View recycleView; 563 | while (!mLoadedViews.isEmpty()) { 564 | recycleViews.add(recycleView = mLoadedViews.remove()); 565 | detachViewFromParent(recycleView); 566 | } 567 | 568 | View currentView = makeAndAddView(position, true, 569 | (recycleViews.isEmpty() ? null : recycleViews.remove(0))); 570 | mLoadedViews.addLast(currentView); 571 | 572 | for(int offset = 1; mSideBuffer - offset >= 0; offset++) { 573 | int leftIndex = position - offset; 574 | int rightIndex = position + offset; 575 | if(leftIndex >= 0) 576 | mLoadedViews.addFirst(makeAndAddView(leftIndex, false, 577 | (recycleViews.isEmpty() ? null : recycleViews.remove(0)))); 578 | if(rightIndex < mAdapter.getCount()) 579 | mLoadedViews.addLast(makeAndAddView(rightIndex, true, 580 | (recycleViews.isEmpty() ? null : recycleViews.remove(0)))); 581 | } 582 | 583 | mCurrentBufferIndex = mLoadedViews.indexOf(currentView); 584 | mCurrentAdapterIndex = position; 585 | 586 | for (View view : recycleViews) { 587 | removeDetachedView(view, false); 588 | } 589 | requestLayout(); 590 | setVisibleView(mCurrentBufferIndex, false); 591 | if (mIndicator != null) { 592 | mIndicator.onSwitched(mLoadedViews.get(mCurrentBufferIndex), 593 | mCurrentAdapterIndex); 594 | } 595 | if (mViewSwitchListener != null) { 596 | mViewSwitchListener 597 | .onSwitched(mLoadedViews.get(mCurrentBufferIndex), 598 | mCurrentAdapterIndex); 599 | } 600 | } 601 | 602 | private void resetFocus() { 603 | mLoadedViews.clear(); 604 | removeAllViewsInLayout(); 605 | 606 | for (int i = Math.max(0, mCurrentAdapterIndex - mSideBuffer); i < Math 607 | .min(mAdapter.getCount(), mCurrentAdapterIndex + mSideBuffer 608 | + 1); i++) { 609 | mLoadedViews.addLast(makeAndAddView(i, true, null)); 610 | if (i == mCurrentAdapterIndex) 611 | mCurrentBufferIndex = mLoadedViews.size() - 1; 612 | } 613 | requestLayout(); 614 | } 615 | 616 | private void postViewSwitched(int direction) { 617 | if (direction == 0) 618 | return; 619 | 620 | if (direction > 0) { // to the right 621 | mCurrentAdapterIndex++; 622 | mCurrentBufferIndex++; 623 | 624 | // if(direction > 1) { 625 | // mCurrentAdapterIndex += mAdapter.getCount() - 2; 626 | // mCurrentBufferIndex += mAdapter.getCount() - 2; 627 | // } 628 | 629 | View recycleView = null; 630 | 631 | // Remove view outside buffer range 632 | if (mCurrentAdapterIndex > mSideBuffer) { 633 | recycleView = mLoadedViews.removeFirst(); 634 | detachViewFromParent(recycleView); 635 | // removeView(recycleView); 636 | mCurrentBufferIndex--; 637 | } 638 | 639 | // Add new view to buffer 640 | int newBufferIndex = mCurrentAdapterIndex + mSideBuffer; 641 | if (newBufferIndex < mAdapter.getCount()) 642 | mLoadedViews.addLast(makeAndAddView(newBufferIndex, true, 643 | recycleView)); 644 | 645 | } else { // to the left 646 | mCurrentAdapterIndex--; 647 | mCurrentBufferIndex--; 648 | 649 | // if(direction < -1) { 650 | // mCurrentAdapterIndex -= mAdapter.getCount() - 2; 651 | // mCurrentBufferIndex -= mAdapter.getCount() - 2; 652 | // } 653 | 654 | View recycleView = null; 655 | 656 | // Remove view outside buffer range 657 | if (mAdapter.getCount() - 1 - mCurrentAdapterIndex > mSideBuffer) { 658 | recycleView = mLoadedViews.removeLast(); 659 | detachViewFromParent(recycleView); 660 | } 661 | 662 | // Add new view to buffer 663 | int newBufferIndex = mCurrentAdapterIndex - mSideBuffer; 664 | if (newBufferIndex > -1) { 665 | mLoadedViews.addFirst(makeAndAddView(newBufferIndex, false, 666 | recycleView)); 667 | mCurrentBufferIndex++; 668 | } 669 | 670 | } 671 | 672 | requestLayout(); 673 | setVisibleView(mCurrentBufferIndex, true); 674 | if (mIndicator != null) { 675 | mIndicator.onSwitched(mLoadedViews.get(mCurrentBufferIndex), 676 | mCurrentAdapterIndex); 677 | } 678 | if (mViewSwitchListener != null) { 679 | mViewSwitchListener 680 | .onSwitched(mLoadedViews.get(mCurrentBufferIndex), 681 | mCurrentAdapterIndex); 682 | } 683 | } 684 | 685 | private View setupChild(View child, boolean addToEnd, boolean recycle) { 686 | ViewGroup.LayoutParams p = (ViewGroup.LayoutParams) child 687 | .getLayoutParams(); 688 | if (p == null) { 689 | p = new AbsListView.LayoutParams( 690 | ViewGroup.LayoutParams.FILL_PARENT, 691 | ViewGroup.LayoutParams.WRAP_CONTENT, 0); 692 | } 693 | if (recycle) 694 | attachViewToParent(child, (addToEnd ? -1 : 0), p); 695 | else 696 | addViewInLayout(child, (addToEnd ? -1 : 0), p, true); 697 | return child; 698 | } 699 | 700 | private View makeAndAddView(int position, boolean addToEnd, View convertView) { 701 | View view = mAdapter.getView(position, convertView, this); 702 | return setupChild(view, addToEnd, convertView != null); 703 | } 704 | 705 | class AdapterDataSetObserver extends DataSetObserver { 706 | 707 | @Override 708 | public void onChanged() { 709 | View v = getChildAt(mCurrentBufferIndex); 710 | if (v != null) { 711 | for (int index = 0; index < mAdapter.getCount(); index++) { 712 | if (v.equals(mAdapter.getItem(index))) { 713 | mCurrentAdapterIndex = index; 714 | break; 715 | } 716 | } 717 | } 718 | resetFocus(); 719 | } 720 | 721 | @Override 722 | public void onInvalidated() { 723 | // Not yet implemented! 724 | } 725 | 726 | } 727 | 728 | public void setTimeSpan(long timeSpan) { 729 | this.timeSpan = timeSpan; 730 | } 731 | 732 | public void setmSideBuffer(int mSideBuffer) { 733 | this.mSideBuffer = mSideBuffer; 734 | } 735 | } 736 | --------------------------------------------------------------------------------