一直想知道这种效果到底是如何做出来的,直到看到代码,原来还是动画。从网上找了两份代码,原理基本相同,两份代码中应该有相互参考部分,现在简单解析下,做一个记录,另外,代码中做了些许不妨碍功能的修改(如果有时间的话,自己也会考虑用fragment实现下)。先看下效果图:
这里主要讲解的是以下部分:
先看下注释里面的说明:
/** * Android实现局部图片滑动指引效果 * @Description: 实现以下功能: * 1、顶部单张图片左右拖拉滑动; * 2、带指引; * 3、仅滑动顶部单张图片,不滑动页面,下面的图文内容不动; * 4、类似于新闻客户端的功能
看下它的主Activity里面的全局变量(也就是上面图形):
public class MainActivity extends ActivityGroup implements OnClickListener{
// 选中的新闻条目
private TextView mSelectedItem = null;
// 头部新闻条目的Layout
private RelativeLayout mHeader = null;
// 中间新闻主体的Layout
private RelativeLayout mNewsMainLayout = null;
private LayoutParams params = null;
//顶部提示
private TextView mNetEaseTop = null;
// 新闻分类
private TextView mNewsItem = null;
private TextView mInfoItem = null;
private TextView mBlogItem = null;
private TextView mMagezineItem = null;
private TextView mDomainItem = null;
private TextView mMoreItem = null;
// 新闻分类中每条分类的宽度
private int mItemWidth = 0;
// 条目背景移动开始位置
private int startX = 0;
private Intent mIntent = null;
// 设置新闻主题
private View mNewsMain = null;注释比较详细。可以看到这个activity继承自activityGroup类,而activityGroup类在3.0以后是deprecated,所以在开头说想要用fragment重新实现一下。
Deprecated. Use the new Fragment and FragmentManager APIs instead; these are also available on older platforms through the Android compatibility package.
再看onCreate方法:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 初始化控件
initeViews();
}
/**
* 初始化控件
*/
private void initeViews(){
mNewsItem = (TextView) findViewById(R.id.tv_title_news);
mInfoItem = (TextView) findViewById(R.id.tv_title_info);
mBlogItem = (TextView) findViewById(R.id.tv_title_blog);
mMagezineItem = (TextView) findViewById(R.id.tv_title_magazine);
mDomainItem = (TextView) findViewById(R.id.tv_title_domain);
mMoreItem = (TextView) findViewById(R.id.tv_title_more);
mNewsItem.setOnClickListener(this);
mInfoItem.setOnClickListener(this);
mBlogItem.setOnClickListener(this);
mMagezineItem.setOnClickListener(this);
mDomainItem.setOnClickListener(this);
mMoreItem.setOnClickListener(this);
// 设置选中条目属性
mSelectedItem = new TextView(this);
mSelectedItem.setText(R.string.title_news_category_tops);
mSelectedItem.setTextColor(Color.WHITE);
mSelectedItem.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17);
mSelectedItem.setGravity(Gravity.CENTER);
mSelectedItem.setWidth((getScreenWidth() - DimensionUtility.dip2px(this, 20)) / 6);
mSelectedItem.setBackgroundResource(R.drawable.slidebar);
RelativeLayout.LayoutParams param = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
param.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
mHeader = (RelativeLayout) findViewById(R.id.layout_title_bar);
mNetEaseTop = (TextView) findViewById(R.id.tv_netease_top);
mHeader.addView(mSelectedItem, param);
// 设置头条新闻主体
mIntent = new Intent(MainActivity.this, TopicNews.class);
mNewsMain = getLocalActivityManager().startActivity(
"TopicNews", mIntent).getDecorView();
params = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
mNewsMainLayout = (RelativeLayout) findViewById(R.id.layout_news_main);
mNewsMainLayout.addView(mNewsMain, params);
}这里所说的选中条目,就是上图中,选中的高亮部分,它其实可以理解为和下面六个是上下两层的关系。而mNetEaseTop是指的
这一块内容,在原代码中,作者并未做这一块和下面内容的同时更新,个人后来加上。设置头条新闻主题下面就是设置默认选择项:第一项(头条新闻)。
再来看一下里面用到的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:weightSum="10" >
<include
android:id="@+id/header"
layout="@layout/header" />
<RelativeLayout
android:id="@+id/layout_news_main"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="9" >
</RelativeLayout>
<RelativeLayout
android:id="@+id/layout_bottom"
android:layout_weight="1"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<RadioGroup
android:id="@+id/radiogroup"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:background="@drawable/bottombg"
android:gravity="center_vertical"
android:orientation="horizontal" >
<RadioButton
android:id="@+id/radio_news"
android:layout_width="wrap_content"
android:background="@drawable/tab_selector_news"
android:button="@null"
android:checked="true" />
<RadioButton
android:id="@+id/radio_topic"
android:layout_width="wrap_content"
android:background="@drawable/tab_selector_topic"
android:button="@null" />
<RadioButton
android:id="@+id/radio_pic"
android:layout_width="wrap_content"
android:background="@drawable/tab_selector_pic"
android:button="@null" />
<RadioButton
android:id="@+id/radio_follow"
android:layout_width="wrap_content"
android:background="@drawable/tab_selector_follow"
android:button="@null" />
<RadioButton
android:id="@+id/radio_vote"
android:layout_width="wrap_content"
android:background="@drawable/tab_selector_vote"
android:button="@null" />
</RadioGroup>
</RelativeLayout>
</LinearLayout>中间的layout_new_main就是mNewsMainLayout,起到一个占位的作用,下面的RadioGroup就是最下面的标记栏。里面用到的布局文件主要是header.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<RelativeLayout
android:id="@+id/layout_top"
android:layout_width="match_parent"
android:layout_height="40dip"
android:background="#990000" >
<TextView
android:id="@+id/tv_netease_top"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="10dip"
android:textSize="20sp"
android:textColor="@android:color/white"
android:text="@string/news_top_left_text1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/tv_netease_top"
android:text="@string/news_top_left_text2"
android:textColor="@android:color/white"
android:textSize="20sp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:src="/blog_article/@drawable/duoyun/index.html"
android:contentDescription="@string/img_duoyun_desc" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/layout_title_bar"
android:layout_width="fill_parent"
android:layout_height="40dip"
android:paddingLeft="5dip"
android:paddingRight="5dip"
android:background="@drawable/bg_header_top">
<LinearLayout
android:id="@+id/header_item"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<RelativeLayout
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" >
<TextView
android:id="@+id/tv_title_news"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:gravity="center"
android:text="@string/title_news_category_tops" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" >
<TextView
android:id="@+id/tv_title_info"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:gravity="center"
android:text="@string/title_news_category_info" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" >
<TextView
android:id="@+id/tv_title_blog"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:gravity="center"
android:text="@string/title_news_category_blog" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" >
<TextView
android:id="@+id/tv_title_magazine"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:gravity="center"
android:text="@string/title_news_category_magazine" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" >
<TextView
android:id="@+id/tv_title_domain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:gravity="center"
android:text="@string/title_news_category_domain" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" >
<TextView
android:id="@+id/tv_title_more"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:gravity="center"
android:text="@string/title_news_category_more" />
</RelativeLayout>
</LinearLayout>
</RelativeLayout>
</LinearLayout>比较简单的布局,不详述。
上面代码设置选中项宽带:
mSelectedItem.setWidth((getScreenWidth() - DimensionUtility.dip2px(this, 20)) / 6);
用到了getScreenWidth方法:
/**
* 获取屏幕的宽度
* @return
*/
private int getScreenWidth(){
WindowManager windowManager = getWindowManager();
Display display = windowManager.getDefaultDisplay();
// Point point = new Point();
// display.getSize(point);
// int screenWidth = point.x;
int screenWidth = display.getWidth();
return screenWidth;
}display的getWidth方法在3.0中好像也没deprecated。可以使用注释掉的代码获取屏幕宽度。
下面是最重要的部分点击切换:
// 新闻分类事件监听
@Override
public void onClick(View v) {
mItemWidth = findViewById(R.id.layout).getWidth();
switch (v.getId()) {
case R.id.tv_title_news:
//动画滑动
ImageAnimation.SetImageSlide(mSelectedItem, startX, 0, 0, 0);
//设置滑动后动画开始位置
startX = 0;
//设置选中项显示文字,也就是高亮部分文字
mSelectedItem.setText(R.string.title_news_category_tops);
//设置左上角提示文字
mNetEaseTop.setText(R.string.title_news_category_tops);
// 显示头条信息
mIntent.setClass(MainActivity.this, TopicNews.class);
mNewsMain = getLocalActivityManager().startActivity(
"TopicNews", mIntent).getDecorView();
break;
case R.id.tv_title_info:
ImageAnimation.SetImageSlide(mSelectedItem, startX, mItemWidth, 0, 0);
startX = mItemWidth;
mSelectedItem.setText(R.string.title_news_category_info);
mNetEaseTop.setText(R.string.title_news_category_info);
// 显示资讯信息
mIntent.setClass(MainActivity.this, InfoNews.class);
mNewsMain = getLocalActivityManager().startActivity(
"InfoNews", mIntent).getDecorView();
break;
case R.id.tv_title_blog:
ImageAnimation.SetImageSlide(mSelectedItem, startX, mItemWidth * 2, 0, 0);
startX = mItemWidth * 2;
mSelectedItem.setText(R.string.title_news_category_blog);
mNetEaseTop.setText(R.string.title_news_category_blog);
// 显示博客信息
mIntent.setClass(MainActivity.this, BlogNews.class);
mNewsMain = getLocalActivityManager().startActivity(
"BlogNews", mIntent).getDecorView();
break;
case R.id.tv_title_magazine:
ImageAnimation.SetImageSlide(mSelectedItem, startX, mItemWidth * 3, 0, 0);
startX = mItemWidth * 3;
mSelectedItem.setText(R.string.title_news_category_magazine);
mNetEaseTop.setText(R.string.title_news_category_magazine);
// 显示杂志信息
mIntent.setClass(MainActivity.this, MagazineNews.class);
mNewsMain = getLocalActivityManager().startActivity(
"MagazineNews", mIntent).getDecorView();
break;
case R.id.tv_title_domain:
ImageAnimation.SetImageSlide(mSelectedItem, startX, mItemWidth * 4, 0, 0);
startX = mItemWidth * 4;
mSelectedItem.setText(R.string.title_news_category_domain);
mNetEaseTop.setText(R.string.title_news_category_domain);
// 显示业界信息
mIntent.setClass(MainActivity.this, DomainNews.class);
mNewsMain = getLocalActivityManager().startActivity(
"DomainNews", mIntent).getDecorView();
break;
case R.id.tv_title_more:
ImageAnimation.SetImageSlide(mSelectedItem, startX, mItemWidth * 5, 0, 0);
startX = mItemWidth * 5;
mSelectedItem.setText(R.string.title_news_category_more);
mNetEaseTop.setText(R.string.title_news_category_more);
// 显示更多信息
mIntent.setClass(MainActivity.this, MoreNews.class);
mNewsMain = getLocalActivityManager().startActivity(
"MoreNews", mIntent).getDecorView();
break;
default:
break;
}
// 更换Layout中的新闻主体
mNewsMainLayout.removeAllViews();
mNewsMainLayout.addView(mNewsMain, params);
}在注释中,解释的已经比较清楚了,看一下ImageAnimation:
public class ImageAnimation {
/**
* 设置图像移动动画效果
* @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);
}
} 就是一个简单的移动动画。这样就是简单的顶部标示就完成了,接下来要分析如下:这个代码在TopicNews中,先看下使用到的全局变量:
public class TopicNews extends Activity{
// 滑动图片的集合
private ArrayList<View> mImagePageViewList = null;
private ViewGroup mMainView = null;
private ViewPager mViewPager = null;
// 当前ViewPager索引
// private int pageIndex = 0;
// 包含圆点图片的View
private ViewGroup mImageCircleView = null;
private ImageView[] mImageCircleViews = null;
// 滑动标题
private TextView mSlideTitle = null;
// 布局设置类
private SlideImageLayout mSlideLayout = null;
// 数据解析类
private NewsXmlParser mParser = null; 里面的viewPager类是用于滑动控件,SlideImageLayout类是用于上面图片和下面标志点的布局。NewsXmlParser类提供数据。
看一下onCreate方法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(android.R.style.Theme_Translucent_NoTitleBar);
// 初始化
initeViews();
}
/**
* 初始化
*/
private void initeViews(){
// 滑动图片区域
mImagePageViewList = new ArrayList<View>();
LayoutInflater inflater = getLayoutInflater();
mMainView = (ViewGroup)inflater.inflate(R.layout.page_topic_news, null);
mViewPager = (ViewPager) mMainView.findViewById(R.id.image_slide_page);
// 圆点图片区域
mParser = new NewsXmlParser();
int length = mParser.getSlideImages().length;
mImageCircleViews = new ImageView[length];
mImageCircleView = (ViewGroup) mMainView.findViewById(R.id.layout_circle_images);
mSlideLayout = new SlideImageLayout(TopicNews.this);
mSlideLayout.setCircleImageLayout(length);
for(int i = 0; i < length; i++){
mImagePageViewList.add(mSlideLayout.getSlideImageLayout(mParser.getSlideImages()[i]));
mImageCircleViews[i] = mSlideLayout.getCircleImageLayout(i);
mImageCircleView.addView(mSlideLayout.getLinearLayout(mImageCircleViews[i], 10, 10));
}
// 设置默认的滑动标题
mSlideTitle = (TextView) mMainView.findViewById(R.id.tvSlideTitle);
mSlideTitle.setText(mParser.getSlideTitles()[0]);
setContentView(mMainView);
// 设置ViewPager
mViewPager.setAdapter(new SlideImageAdapter());
mViewPager.setOnPageChangeListener(new ImagePageChangeListener());
}看一下远点图片区域:
// 圆点图片区域
mParser = new NewsXmlParser();
int length = mParser.getSlideImages().length;
mImageCircleViews = new ImageView[length];
mImageCircleView = (ViewGroup) mMainView.findViewById(R.id.layout_circle_images);
mSlideLayout = new SlideImageLayout(TopicNews.this);
mSlideLayout.setCircleImageLayout(length);
for(int i = 0; i < length; i++){
mImagePageViewList.add(mSlideLayout.getSlideImageLayout(mParser.getSlideImages()[i]));
mImageCircleViews[i] = mSlideLayout.getCircleImageLayout(i);
mImageCircleView.addView(mSlideLayout.getLinearLayout(mImageCircleViews[i], 10, 10));
}里面主要是调用了NewsXmlParser类和SlideImageLayout的方法,那就先看下NewsXmlParser的getSlideImages方法:public int[] getSlideImages(){
return slideImages;
}这里的slideImage是在类中定义好的:// 滑动图片的集合,这里设置成了固定加载,当然也可动态加载。
private int[] slideImages = {
R.drawable.image01,
R.drawable.image02,
R.drawable.image03,
R.drawable.image04,
R.drawable.image05}; 那看一下用到的SlideImageLayout类的getSlideImageLayout:获取图片的布局:/**
* 生成滑动图片区域布局
* @param id
* @return
*/
public View getSlideImageLayout(int id){
// 包含TextView的LinearLayout
LinearLayout imageLinerLayout = new LinearLayout(mContext);
LinearLayout.LayoutParams imageLinerLayoutParames = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
1);
ImageView iv = new ImageView(mContext);
iv.setBackgroundResource(id);
iv.setOnClickListener(new ImageOnClickListener());
imageLinerLayout.addView(iv,imageLinerLayoutParames);
mImageList.add(iv);
return imageLinerLayout;
}创建一个layout,然后再创建一个imageView,把ImageView加入到layout中,然后返回layout。同理,我们再看下set和getCircleImageLayout:
* 设置圆点个数
* @param size
*/
public void setCircleImageLayout(int size){
mImageViews = new ImageView[size];
}
/**
* 生成圆点图片区域布局对象
* @param index
* @return
*/
public ImageView getCircleImageLayout(int index){
mImageView = new ImageView(mContext);
mImageView.setLayoutParams(new LayoutParams(10,10));
mImageView.setScaleType(ScaleType.FIT_XY);
mImageViews[index] = mImageView;
if (index == 0) {
//默认选中第一张图片
mImageViews[index].setBackgroundResource(R.drawable.dot_selected);
} else {
mImageViews[index].setBackgroundResource(R.drawable.dot_none);
}
return mImageViews[index];
}获取圆点图片的Image,然后返回。
在initViews中还有设置滑动图片标题:
// 设置默认的滑动标题 mSlideTitle = (TextView) mMainView.findViewById(R.id.tvSlideTitle); mSlideTitle.setText(mParser.getSlideTitles()[0]); setContentView(mMainView);
最后是设置Viewpager的Adapter还有监听
// 设置ViewPager
mViewPager.setAdapter(new SlideImageAdapter());
mViewPager.setOnPageChangeListener(new ImagePageChangeListener());先来看下SlideImageAdapter类:
// 滑动图片数据适配器
private class SlideImageAdapter extends PagerAdapter {
@Override
public int getCount() {
return mImagePageViewList.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public int getItemPosition(Object object) {
return super.getItemPosition(object);
}
@Override
public void destroyItem(View view, int arg1, Object arg2) {
((ViewPager) view).removeView(mImagePageViewList.get(arg1));
}
@Override
public Object instantiateItem(View view, int position) {
((ViewPager) view).addView(mImagePageViewList.get(position));
return mImagePageViewList.get(position);
}
@Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
}
@Override
public Parcelable saveState() {
return null;
}
@Override
public void startUpdate(View arg0) {
}
@Override
public void finishUpdate(View arg0) {
}
}一个典型的适配器类,主要看下面两个方法:
@Override
public void destroyItem(View view, int arg1, Object arg2) {
((ViewPager) view).removeView(mImagePageViewList.get(arg1));
}
@Override
public Object instantiateItem(View view, int position) {
((ViewPager) view).addView(mImagePageViewList.get(position));
return mImagePageViewList.get(position);
} 实例化Item和销毁Item。我们在initViews方法里面为mImagePageViewList里面加载了很多view,在这里取出,加入到ViewPager中去。其实他的滑动监听事件特别简单,就是改变下选中图片、标识圆点和标题文字:
// 滑动页面更改事件监听器
private class ImagePageChangeListener implements OnPageChangeListener {
@Override
public void onPageScrollStateChanged(int arg0) {
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
@Override
public void onPageSelected(int index) {
// pageIndex = index;
mSlideLayout.setPageIndex(index);
mSlideTitle.setText(mParser.getSlideTitles()[index]);
for (int i = 0; i < mImageCircleViews.length; i++) {
mImageCircleViews[index].setBackgroundResource(R.drawable.dot_selected);
if (index != i) {
mImageCircleViews[i].setBackgroundResource(R.drawable.dot_none);
}
}
}
} 还有,在网易新闻里,有个底部标记栏:
这个功能是如何实现的呢?
<RelativeLayout
android:id="@+id/layout_bottom"
android:layout_weight="1"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<RadioGroup
android:id="@+id/radiogroup"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:background="@drawable/bottombg"
android:gravity="center_vertical"
android:orientation="horizontal" >
<RadioButton
android:id="@+id/radio_news"
android:layout_width="wrap_content"
android:background="@drawable/tab_selector_news"
android:button="@null"
android:checked="true" />
<RadioButton
android:id="@+id/radio_topic"
android:layout_width="wrap_content"
android:background="@drawable/tab_selector_topic"
android:button="@null" />
<RadioButton
android:id="@+id/radio_pic"
android:layout_width="wrap_content"
android:background="@drawable/tab_selector_pic"
android:button="@null" />
<RadioButton
android:id="@+id/radio_follow"
android:layout_width="wrap_content"
android:background="@drawable/tab_selector_follow"
android:button="@null" />
<RadioButton
android:id="@+id/radio_vote"
android:layout_width="wrap_content"
android:background="@drawable/tab_selector_vote"
android:button="@null" />
</RadioGroup>
</RelativeLayout>其中background图片:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/current_topic_tab" android:state_checked="true"/>
<item android:drawable="@drawable/back_topic_tab" android:state_checked="false"/>
</selector>切换代码:
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.radio_news:
ImageAnimation.SetImageSlide(mImageView, startLeft, 0, 0, 0);
startLeft = 0;
break;
case R.id.radio_topic:
ImageAnimation.SetImageSlide(mImageView, startLeft, mImageView.getWidth(), 0, 0);
startLeft = mImageView.getWidth();
break;
case R.id.radio_pic:
ImageAnimation.SetImageSlide(mImageView, startLeft, mImageView.getWidth() * 2, 0, 0);
startLeft = mImageView.getWidth() * 2;
break;
case R.id.radio_follow:
ImageAnimation.SetImageSlide(mImageView, startLeft, mImageView.getWidth() * 3, 0, 0);
startLeft = mImageView.getWidth() * 3;
break;
case R.id.radio_vote:
ImageAnimation.SetImageSlide(mImageView, startLeft, mImageView.getWidth() * 4, 0, 0);
startLeft = mImageView.getWidth() * 4;
break;
default:
break;
}
最后代码下载地址:
http://download.csdn.net/detail/aomandeshangxiao/4751356
问题描述:
在我的系统中,目前modem通过外部中断可以唤醒AP。当AP进入睡眠的时候,如果modem收到incoming call或者incoming sms或者其他网络事件,modem就会拉高拉低一下AP的外部中断脚,这时候,AP就会被wakeup了。现在的问题是,当incoming call或者incoming SMS来的时候,可以唤醒AP,但系统只进入到Resume状态,然后迅速又进入睡眠,系统不会从resume状态切换到systemon状态,或者说android被唤醒之后发现没有什么事件需要它处理,然后又进入睡眠了。
因为RIL源码华为不开放,只能参照原生态的RIL来跟踪问题,在原生的RIL里面,没有对modem的这个中断做任何处理。在/kernel/power/suspend.c的suspend_finish()函数中,使用pm_notifier_call_chain(PM_POST_SUSPEND);广播一个系统从suspend状态退出的notify,发出这个消息后RIL进程就唤醒了,然后RIL不断POLLING USB端口的数据,如果发现数据端口有incoming call和incoming sms事件,就告诉android有事件要处理,这时候android就会写一个on到/sys/power/state中,这时候系统才真正的唤醒,开始走linux
resume和late resume的流程,打开LCD,处理incoming call,打开APK显示来电等。现在的状况是,RIL在被唤醒后,POLLING USB数据端口,发现没有任何数据,也就不会告诉android有incoming call事件了,android发现没有什么事情需要处理,就又进入睡眠。查看resume后的打印信息发现,kernel resume后连接modem的usb reset了,LOG如下:
[ 79.970069] usb 1-3: reset high speed USB device number 2 using s5p-ehci [ 80.315189] GPS: mt3326_gps_resume: [ 80.317320] GPS: mt3326_gps_set_suspend: issue sysfs_notify : d2369270 [ 80.390143] usb 1-3.2: reset high speed USB device number 3 using s5p-ehci [ 80.502360] PM: resume of devices complete after 909.113 msecs
这就不难解释为什么RIL POLLING不到数据了。由于没有RIL源码,无法DEBUG到RIL POLLING的信息,没办法100%确认这个结论。
所以,个人认为,来电无法唤醒的这个问题应该是HSIC接口RESET导致的,如果USB掉电->HSIC RESET无法避免,那就只有修改RIL和MODEM FIRMWARE,增加握手信号,在MODEM和RIL都确认系统唤醒可以正常工作后再开始数据业务。
gallery3d的源码分析很多,有些也很透彻。我的源码分析的参考资料也是来源于网络。
gallery3d的入口代码在gallery.java文件。首先来分析入口做了哪些事情。
uper.onCreate(savedInstanceState);
final boolean imageManagerHasStorage = ImageManager.hasStorage();
boolean slideshowIntent = false;
if (isViewIntent()) {
Bundle extras = getIntent().getExtras();
Log.i(TAG, "Gallery, onCreate, isViewIntent");
if (extras != null) {
slideshowIntent = extras.getBoolean("slideshow", false);
Log.i(TAG, "Gallery, onCreate, isViewIntent, slideshowIntent:"+slideshowIntent);
}
}
Log.i(TAG, "Images.Media.EXTERNAL_CONTENT_URI:"+Images.Media.EXTERNAL_CONTENT_URI.toString());
if (isViewIntent() && getIntent().getData().equals(Images.Media.EXTERNAL_CONTENT_URI)
&& slideshowIntent) {
if (!imageManagerHasStorage) {
Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
finish();
} else {
Slideshow slideshow = new Slideshow(this);
slideshow.setDataSource(new RandomDataSource());
setContentView(slideshow);
mDockSlideshow = true;
Log.i(TAG, "Gallery, onCreate, isViewIntent, Slideshow");
}
return;
}
入口首先检测是否有来自用户的看图动作。gallery3d的功能强大,不仅可以像ACDsee那样看图片,还可以播放mp4等格式的视频,例如可以播放手机视频客户端的视频,不信你可以试试。
接下来获得屏幕的密度:
if (PIXEL_DENSITY == 0.0f) {
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
PIXEL_DENSITY = metrics.density;
}
接下来的代码就是入口的精华:
mReverseGeocoder = new ReverseGeocoder(this);
mRenderView = new RenderView(this);
mGridLayer = new GridLayer(this, (int) (96.0f * PIXEL_DENSITY), (int) (72.0f * PIXEL_DENSITY), new GridLayoutInterface(4),
mRenderView);
mRenderView.setRootLayer(mGridLayer);
setContentView(mRenderView);
;
ReverseGeocoder是获取照片位置的thread,关键函数就是computeMostGranularCommonLocation。
RenderView类是最核心的类,继承于GLSurfaceView,Gallery3D都是围绕这个类来实现界面的渲染和事件的处理。
GridLayer对象则是opengl surface上最核心的layer。这里说明了每个item即每张缩略图的大小,宽为96dpi,高位72dpi,可见视图最多显示4行缩略图。缩略图的横向间距是20dpi,纵向间距是40dpi。
最后setContentView(mRenderView)设置当前view为RenderView的对象,说明Gallery3D所有界面都是opengl渲染的,跟android基础UI没有任何关系。这其实不是件好事,毕竟opengl对硬件要求比较高,低端机器使用gallery3d效果不是很好,大家可以做对比。
界面准备好了,但是数据从何而来呢?请看下面:
Thread t = new Thread() {
public void run() {
int numRetries = 25;
if (!imageManagerHasStorage) {
showToast(getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG);
do {
--numRetries;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
;
}
} while (numRetries > 0 && !ImageManager.hasStorage());
}
final boolean imageManagerHasStorageAfterDelay = ImageManager.hasStorage();
Log.i(TAG, "Gallery:onCreate, thread");
CacheService.computeDirtySets(Gallery.this);
CacheService.startCache(Gallery.this, false);
final boolean isCacheReady = CacheService.isCacheReady(false);
// Creating the DataSource objects.
final PicasaDataSource picasaDataSource = new PicasaDataSource(Gallery.this);
final LocalDataSource localDataSource = new LocalDataSource(Gallery.this);
final ConcatenatedDataSource combinedDataSource = new ConcatenatedDataSource(localDataSource, picasaDataSource);
// Depending upon the intent, we assign the right dataSource.
if (!isPickIntent() && !isViewIntent()) {
if (imageManagerHasStorageAfterDelay) {
mGridLayer.setDataSource(combinedDataSource);
} else {
mGridLayer.setDataSource(picasaDataSource);
}
if (!isCacheReady && imageManagerHasStorageAfterDelay) {
showToast(getResources().getString(R.string.loading_new), Toast.LENGTH_LONG);
}
} else if (!isViewIntent()) {
final Intent intent = getIntent();
if (intent != null) {
final String type = intent.resolveType(Gallery.this);
boolean includeImages = isImageType(type);
boolean includeVideos = isVideoType(type);
((LocalDataSource) localDataSource).setMimeFilter(!includeImages, !includeVideos);
if (includeImages) {
if (imageManagerHasStorageAfterDelay) {
mGridLayer.setDataSource(combinedDataSource);
} else {
mGridLayer.setDataSource(picasaDataSource);
}
} else {
mGridLayer.setDataSource(localDataSource);
}
mGridLayer.setPickIntent(true);
if (!imageManagerHasStorageAfterDelay) {
showToast(getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG);
} else {
showToast(getResources().getString(R.string.pick_prompt), Toast.LENGTH_LONG);
}
}
} else {
// View intent for images.
Uri uri = getIntent().getData();
Log.i(TAG, "Gallery, view intent for images, uri:"+uri.toString());
boolean slideshow = getIntent().getBooleanExtra("slideshow", false);
final SingleDataSource singleDataSource = new SingleDataSource(Gallery.this, uri.toString(), slideshow);
final ConcatenatedDataSource singleCombinedDataSource = new ConcatenatedDataSource(singleDataSource, picasaDataSource);
mGridLayer.setDataSource(singleCombinedDataSource);
mGridLayer.setViewIntent(true, Utils.getBucketNameFromUri(uri));
if (singleDataSource.isSingleImage()) {
Log.i(TAG, "Gallery, view intent for images, set single image");
mGridLayer.setSingleImage(false);
} else if (slideshow) {
mGridLayer.setSingleImage(true);
Log.i(TAG, "Gallery, view intent for images, start slide show");
mGridLayer.startSlideshow();
}
}
}
};
t.start();
这段代码说明了数据从哪里来的。首先检查有没有外部存储设备,例如SD卡。如果没有就提示用户没有SD卡。
接着调用CacheService.computeDirtySets(Gallery.this)查看是否有新的相册或者视频。CacheService继承IntentService,CacheService.startCache(Gallery.this, false)启动这个service,然后这个service会处理新的相册或者视频,将其缓存到SD卡中,方便再次使用时直接从cache获取,快速显示。
数据源有几种:PicasaDataSource,LocalDataSource,ConcatenatedDataSource,SingleDataSource。其中PicasaDataSource是google提供的一种picasa图片服务,可以在这儿看到:http://picasa.google.com。LocalDataSource很好理解,就是储存在本地sd卡的数据源;ConcatenatedDataSource则是LocalDataSource和PicasaDataSource的结合体;SingleDataSource则是针对单张图片浏览时提供的数据源对象。
标准的数据源设置都是mGridLayer.setDataSource(combinedDataSource),即本地数据源和picasa数据源的结合。其实笔者猜想gallery3d是为了将本地图片和picasa连接起来,实现图片分享功能,可惜picasa不怎么成功,倒是gallery3d做得不错。
GridLayer的setDataSource做了什么?
public void setDataSource(DataSource dataSource) {
MediaFeed feed = mMediaFeed;
if (feed != null) {
feed.shutdown();
sDisplayList.clear();
mBackground.clear();
}
mMediaFeed = new MediaFeed(mContext, dataSource, this);
mMediaFeed.start();
}
这个函数就是将数据源导入,显示到屏幕。细节后续再说,敬请期待。