简介
今天主要是应用详情界面的完成。我们把此页面分成4个模块,每一个模块都有一个单独的类完成,这样做是采取了模块化的思想,减少activity的代码量,使代码更加简洁。
另一个重点就是nineOldAndroid.jar的使用
应用详情界面效果图:
4个模块
- AppInfoPart.java app详情
- AppInfoSafePart.java app安全
- AppInfoScreenPart.java app截图
- AppInfoDesPart.java app介绍
AppDetailActivity.java
activity的布局中有scrollview,我们把这4个模块的view放入此scrollview,这样就可以滑动页面了。
activity_detail.XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ScrollView
android:id="@+id/scrollView_detail_activity"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" >
<LinearLayout
android:id="@+id/layout_parts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
<!-- 下载界面 -->
<LinearLayout
android:id="@+id/layout_bottom"
android:layout_width="match_parent"
android:layout_height="55dp"
android:background="@drawable/detail_bottom_bg"
android:orientation="horizontal">
</LinearLayout>
</LinearLayout>
代码AppDetailActivity.java
仍然使用ContentPager管理activity,我们把这4个模块的view放入scrollview,这样就可以滑动页面了
仍然使用ContentPager管理activity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
contentPager = new ContentPager(this) {
@Override
public Object requestData() {
return getData();
}
@Override
public View createSuccessView() {
return getSuccessView();
}
};
setContentView(contentPager);
initActionBar();
}
这4个模块的view放入scrollview
protected View getSuccessView() {
rootView = View.inflate(context, R.layout.activity_detail, null);
scrollvi1ew = (ScrollView) rootView.findViewById(R.id.scrollView_detail_activity);//id为scrollView 的话,获取的是pulltorefresh的id
layout_parts = (LinearLayout) rootView.findViewById(R.id.layout_parts);
layout_bottom = (LinearLayout) rootView.findViewById(R.id.layout_bottom);
part1 = new AppInfoPart();
layout_parts.addView(part1.getView());
part2 = new AppInfoSafePart();
layout_parts.addView(part2.getView());
part3 = new AppInfoScreenPart();
layout_parts.addView(part3.getView());
part4 = new AppInfoDesPart();
layout_parts.addView(part4.getView(scrollvi1ew));
return rootView;
}
怎么实现点击actionbar的导航图标实现退出该页面?
主要是找到导航图标的id,若id相同,则调用 finish()方法退出,Actionbar的导航图标的id是:android.R.id.home
效果图:
代码:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
//点击导航栏,让其消失
if (item.getItemId() == android.R.id.home) {
finish();
}
return super.onOptionsItemSelected(item);
}
下面是这4个模块
4个模块的逻辑都是一样的,类中有2个方法,getView()+setData(),getView()是返回该模块的view,setData()是给模块中的view赋值
AppInfoPart.java
效果图:
代码:
public class AppInfoPart {
private Context context;
private View part1View;
private ImageView iv_part1;
private TextView tv_name_part1;
private RatingBar ratingBar;
private TextView tv_download_number_part1;
private TextView tv_version_part1;
private TextView tv_date_part1;
private TextView tv_size_part1;
public AppInfoPart() {
super();
context = MyApplication.getContext();
}
//Step1:获取该模块的view
public View getView() {
part1View = View.inflate(context, R.layout.part1_app_info, null);
findViews();
return part1View;
}
private void findViews() {
iv_part1 = (ImageView) part1View.findViewById(R.id.iv_part1);
tv_name_part1 = (TextView) part1View.findViewById(R.id.tv_app_name_part1);
ratingBar = (RatingBar) part1View.findViewById(R.id.ratingBar);
tv_download_number_part1 = (TextView) part1View.findViewById(R.id.tv_download_number_part1);
tv_version_part1 = (TextView) part1View.findViewById(R.id.tv_version_part1);
tv_date_part1 = (TextView) part1View.findViewById(R.id.tv_date_part1);
tv_size_part1 = (TextView) part1View.findViewById(R.id.tv_size_part1);
}
//Step2:给模块中view赋值
public void setData(AppInfoBean info) {
ImageLoader.getInstance().displayImage(NetUrl.IMAGE_PREFIX + info.getIconUrl(), iv_part1, ImageLoaderOptions.options);
tv_name_part1.setText(info.getName());
ratingBar.setRating(info.getStars());
tv_download_number_part1.setText("下载:"+info.getDownloadNum());
tv_version_part1.setText("版本:"+info.getVersion());
tv_date_part1.setText("日期:"+info.getDate());
tv_size_part1.setText("大小:"+Formatter.formatFileSize(context, info.getSize()));
}
}
AppInfoSafePart.java
这个是上下部分都有动画的,上面的id是ll1_part2,下面的id是:ll2_part2,这2个linearlayout布局不使用
android:visibility=""
属性,是由nineoldandroids-2.4.0.jar实现的动画效果。但ll2_part2里面的3个linearlayout(ll3+ll4+ll5)的显示通过android:visibility=""
来实现。
效果图
代码
3个标签的显示
由于“官方”+“安全”+“无广告”这3个标签必须有一个,而后2个可能没有,所以我们在设置数据的时候,我们要判断第二个和第三个是否需啊哟显示
ArrayList<SafeInfo> safeList = info.getSafe();
// 设置第一行
SafeInfo safeInfo1 = safeList.get(0);
ImageLoader.getInstance().displayImage(NetUrl.IMAGE_PREFIX + safeInfo1.getSafeUrl(), iv1_ll1_part2, ImageLoaderOptions.pagerOptions);
ImageLoader.getInstance().displayImage(NetUrl.IMAGE_PREFIX + safeInfo1.getSafeDesUrl(), iv_ll3_part2, ImageLoaderOptions.pagerOptions);
tv_ll3_part2.setText("" + safeInfo1.getSafeDes());
// 设置第二行
if (safeList.size() > 1) {
ll4_part2.setVisibility(View.VISIBLE);
SafeInfo safeInfo2 = safeList.get(1);
ImageLoader.getInstance().displayImage(NetUrl.IMAGE_PREFIX + safeInfo2.getSafeUrl(), iv2_ll1_part2, ImageLoaderOptions.pagerOptions);
ImageLoader.getInstance().displayImage(NetUrl.IMAGE_PREFIX + safeInfo2.getSafeDesUrl(), iv_ll4_part2, ImageLoaderOptions.pagerOptions);
tv_ll4_part2.setText("" + safeInfo2.getSafeDes());
} else {
ll4_part2.setVisibility(View.GONE);
}
// 设置第三行
if (safeList.size() > 2) {
ll5_part2.setVisibility(View.VISIBLE);
SafeInfo safeInfo3 = safeList.get(2);
ImageLoader.getInstance().displayImage(NetUrl.IMAGE_PREFIX + safeInfo3.getSafeUrl(), iv3_ll1_part2, ImageLoaderOptions.pagerOptions);
ImageLoader.getInstance().displayImage(NetUrl.IMAGE_PREFIX + safeInfo3.getSafeDesUrl(), iv_ll5_part2, ImageLoaderOptions.pagerOptions);
tv_ll5_part2.setText("" + safeInfo3.getSafeDes());
} else {
ll5_part2.setVisibility(View.GONE);
}
ll1_part2动画的实现
先让ll1左移出屏幕,再右移显示,动画时长350ms,增加插值器效果。这也需要计算中屏幕的宽
直接移出屏幕用类ViewHelper,实现移入屏幕的动画效果用类:ViewPropertyAnimator
效果图:
//设置从左向右出现
WindowManager windowManager = (WindowManager) context.getSystemService(context.WINDOW_SERVICE);
int width = windowManager.getDefaultDisplay().getWidth();
ViewHelper.setTranslationX(ll1_part2, -width);
ViewPropertyAnimator.animate(ll1_part2)
.setDuration(5000)
// .setInterpolator(new OvershootInterpolator(5))//弹性的插值器,参数是次数
.setInterpolator(new BounceInterpolator())//球落地的插值器
// .setInterpolator(new CycleInterpolator(1))//左右两边的插值器,一次包含:向左一次 + 向右一次,最后回到复位。要注释掉ViewHelper.setTranslationX(ll1_part2, -width);
.translationXBy(width)
.setStartDelay(500)
.start();
ll2_part2动画的实现
ll2是在显示与隐藏之间切换,也就是高度值在0-maxHeight之间变化,所以需要计算中ll2全部显示是的高度及maxHeight。
步骤:
- 计算中ll2的minHeight和maxHeight
- 将ll2的高设置minHeight(0)
- 将ll2的高度在[minHeight,maxHeight]之间变化
// 设置值动画
// 1求出 ll2_part2 本来的高度
ll2_part2.measure(0, 0);
maxHeight = ll2_part2.getMeasuredHeight();
// 2 隐藏ll2_part2,将高度设置0
ll2_part2.getLayoutParams().height = 0;
ll2_part2.requestLayout();
其中flag是boolean,表示ll2是否显示
ValueAnimator valueAnimator = null;
if (flag) {
valueAnimator = valueAnimator.ofInt(maxHeight, 0);
} else {
valueAnimator = valueAnimator.ofInt(0, maxHeight);
}
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int value = (Integer) valueAnimator.getAnimatedValue();
ll2_part2.getLayoutParams().height = value;
ll2_part2.requestLayout();
}
});
valueAnimator.addListener(listener);
valueAnimator.setDuration(300).start();
private AnimatorListener listener = new AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
// 直接设置前景色/背景色 没有动画效果
// iv_arrow_part2.setImageResource(flag? R.drawable.arrow_up :R.drawable.arrow_down);
ViewPropertyAnimator.animate(iv_arrow_part2).rotationBy(180).setDuration(300).start();
// rotation(180)只转一次
// ViewPropertyAnimator.animate(iv_arrow_part2)
// .setDuration(350)
// .rotationBy(180)
// .setInterpolator(new CycleInterpolator(3))//执行3次动画
// .start();
}
@Override
public void onAnimationRepeat(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
flag = !flag;
}
@Override
public void onAnimationCancel(Animator animator) {
}
};
AppInfoScreenPart.java
主要是photoview的使用
效果图
代码
public class ImageScalActivity extends Activity {
private List<String> screenList;
private int currentPager;
private HackyViewPager viewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_scal);
viewPager = (HackyViewPager) findViewById(R.id.hackyViewPager);
Intent intent = getIntent();
screenList = intent.getStringArrayListExtra("screenList");
currentPager = intent.getIntExtra("currentPager", 0);
Part3Adapter adapter = new Part3Adapter(screenList);
viewPager.setAdapter(adapter);
viewPager.setCurrentItem(currentPager);
}
}
public class Part3Adapter extends BasePagerAdapter<String> {
public Part3Adapter(List<String> list) {
super(list);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
String url = list.get(position);
PhotoView photoView = new PhotoView(container.getContext());
ImageLoader.getInstance().displayImage(NetUrl.IMAGE_PREFIX + url, photoView, ImageLoaderOptions.pagerOptions);
container.addView(photoView);
return photoView;
}
}
AppInfoDesPart.java
高度的变化也是有ValueAnimator实现的,关键是minHeight怎么获取。
效果图
代码
获取 5行和全部显示的高度
//首先:获取 5行和全部显示的高度
//第一步:获取minHeight
tv_desc.setMaxLines(5);
tv_desc.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
//当tv_desc在父View中执行完layout之后回调该方法
@Override
public void onGlobalLayout() {
//监听器用完要立即remove掉,否则只要tv_des的高度变化,又会引起重新layout
tv_desc.getViewTreeObserver().removeGlobalOnLayoutListener(this);
minHeight = tv_desc.getMeasuredHeight();
//第二步:获取maxHeight
tv_desc.setMaxLines(Integer.MAX_VALUE);//设置高度要放在getViewTreeObserver前面,否则怎么取值
tv_desc.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//监听器用完要立即remove掉,否则只要tv_des的高度变化,又会引起重新layout
tv_desc.getViewTreeObserver().removeGlobalOnLayoutListener(this);
maxHeight = tv_desc.getMeasuredHeight();
tv_desc.getLayoutParams().height = minHeight;
tv_desc.requestLayout();
}
});
}
});
下面获取高度的方式是错误的,无法获取5行是的高度和全部高度
当view的内容比view显示出来的宽高要大的时候,就不能用getMeasured方法来获取宽高了
//1.获取5行时候的高度,
tv_des.setMaxLines(5);//设置最大显示5行
tv_des.measure(0, 0);//测量,保证能s够获取到值
int minHeight = tv_des.getMeasuredHeight();
剩下的和ll2一样
ValueAnimator valueAnimator = null;
if (flag) {
valueAnimator = ValueAnimator.ofInt(maxHeight,minHeight);
} else {
valueAnimator = ValueAnimator.ofInt(minHeight,maxHeight);
// scrollview.scrollBy(0, 1000);//放这里无效
}
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int value = (Integer) valueAnimator.getAnimatedValue();
tv_desc.getLayoutParams().height = value;
tv_desc.requestLayout();
//放这里才有效
if (!flag) {//未全部显示时
scrollview.scrollBy(0, 1000);
}
}
});
valueAnimator.addListener(listener);
valueAnimator.setDuration(350).start();
首页listview的item的动画实现
by是增加了多少,没有x是原来的多少倍
效果图:
代码
@Override
public View getView(int position, View convertView, ViewGroup parent) {
...
//初始值设置0.5
ViewHelper.setScaleX(convertView, 0.5f);
ViewHelper.setScaleY(convertView, 0.5f);
ViewPropertyAnimator.animate(convertView)
.scaleXBy(0.5f)//by:比上一步的值增加了几倍 ,原来是0.5,增加0.5,还是1
.scaleYBy(0.5f)
// .scaleX(1.0f)//还原正常大小
// .scaleY(1.0f)//
.setInterpolator(new OvershootInterpolator())//默认也是这个
.setDuration(500)
.start();
...
}
nineoldandroid.jar使用总结
补间动画有平移+缩放+透明度+旋转这4个操作,
若没有动画效果(持续时间),直接是结果,那么用类:ViewHelper
如果有动画效果(持续时间),那么用类ViewPropertyAnimator
如果是View高度变化的动画,那么用类ValueAnimator
源码
上:https://git.oschina.net/googlepalycqc/GooglePlayCQCDay05
下:https://git.oschina.net/googlepalycqc/GooglePlayCQCDay05-2