手机助手(五):模块化开发 + nineOldAndroid.jar

本文详细介绍了一个应用详情页面的设计与实现过程,采用模块化思想将页面分为四个部分:应用基本信息、安全信息、截图展示与应用介绍。文章深入探讨了nineOldAndroids库在动画效果上的应用,并展示了如何通过ValueAnimator实现高度变化的动画。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

今天主要是应用详情界面的完成。我们把此页面分成4个模块,每一个模块都有一个单独的类完成,这样做是采取了模块化的思想,减少activity的代码量,使代码更加简洁。
另一个重点就是nineOldAndroid.jar的使用

应用详情界面效果图:
这里写图片描述

4个模块

  1. AppInfoPart.java app详情
  2. AppInfoSafePart.java app安全
  3. AppInfoScreenPart.java app截图
  4. 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值