高大上的下拉刷新动画

本篇博客代码下载地址:https://github.com/Yalantis/Taurus

最近在github上看到了好多高端、大气、上档次的动画效果,如果给你的项目中加上这些动画,相信你的app一定很优秀,今天给大家分析一下来自Yalantis的一个超好看的下拉刷新动画。

首先我们看一下效果如何:



怎么样?是不是很高大上?接下来我们看一下代码:

一、首先我们需要自定义刷新的动态RefreshView(也就是下拉时候的头)

1.初始化头所占用的Dimens

[java]  view plain copy print ?
  1. private void initiateDimens() {  
  2.         mScreenWidth = mContext.getResources().getDisplayMetrics().widthPixels;  
  3.         mJetTopOffset = mParent.getTotalDragDistance() * 0.5f;  
  4.         mTop = -mParent.getTotalDragDistance();  
  5.     }  

2.为头填充图片,设置图片的大小

分别为左边的云彩,右边的云彩,中间的云彩还有中间的飞机,飞机是带有动画的,下面会介绍飞机的动画
[java]  view plain copy print ?
  1. private void createBitmaps() {  
  2.         mLeftClouds = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.clouds_left);  
  3.         mRightClouds = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.clouds_right);  
  4.         mFrontClouds = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.clouds_center);  
  5.         mJet = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.airplane);  
  6.   
  7.         mJetWidthCenter = mJet.getWidth() / 2;  
  8.         mJetHeightCenter = mJet.getHeight() / 2;  
  9.         mFrontCloudWidthCenter = mFrontClouds.getWidth() / 2;  
  10.         mFrontCloudHeightCenter = mFrontClouds.getHeight() / 2;  
  11.   
  12.         mRightCloudsWidthCenter = mRightClouds.getWidth() / 2;  
  13.         mRightCloudsHeightCenter = mRightClouds.getHeight() / 2;  
  14.         mLeftCloudsWidthCenter = mLeftClouds.getWidth() / 2;  
  15.         mLeftCloudsHeightCenter = mLeftClouds.getHeight() / 2;  
  16.     }  

3.然后我们来画这个头

[java]  view plain copy print ?
  1. public void draw(@NonNull Canvas canvas) {  
  2.         final int saveCount = canvas.save();  
  3.   
  4.         // DRAW BACKGROUND.  
  5.         canvas.drawColor(mContext.getResources().getColor(R.color.sky_background));  
  6.   
  7.         if (isRefreshing) {  
  8.             // Set up new set of wind  
  9.             while (mWinds.size() < WIND_SET_AMOUNT) {  
  10.                 float y = (float) (mParent.getTotalDragDistance() / (Math.random() * RANDOM_Y_COEFFICIENT));  
  11.                 float x = random(MIN_WIND_X_OFFSET, MAX_WIND_X_OFFSET);  
  12.   
  13.                 // Magic with checking interval between winds  
  14.                 if (mWinds.size() > 1) {  
  15.                     y = 0;  
  16.                     while (y == 0) {  
  17.                         float tmp = (float) (mParent.getTotalDragDistance() / (Math.random() * RANDOM_Y_COEFFICIENT));  
  18.   
  19.                         for (Map.Entry<Float, Float> wind : mWinds.entrySet()) {  
  20.                             // We want that interval will be greater than fifth part of draggable distance  
  21.                             if (Math.abs(wind.getKey() - tmp) > mParent.getTotalDragDistance() / RANDOM_Y_COEFFICIENT) {  
  22.                                 y = tmp;  
  23.                             } else {  
  24.                                 y = 0;  
  25.                                 break;  
  26.                             }  
  27.                         }  
  28.                     }  
  29.                 }  
  30.   
  31.                 mWinds.put(y, x);  
  32.                 drawWind(canvas, y, x);  
  33.             }  
  34.   
  35.             // Draw current set of wind  
  36.             if (mWinds.size() >= WIND_SET_AMOUNT) {  
  37.                 for (Map.Entry<Float, Float> wind : mWinds.entrySet()) {  
  38.                     drawWind(canvas, wind.getKey(), wind.getValue());  
  39.                 }  
  40.             }  
  41.   
  42.             // We should to create new set of winds  
  43.             if (mInverseDirection && mNewWindSet) {  
  44.                 mWinds.clear();  
  45.                 mNewWindSet = false;  
  46.                 mWindLineWidth = random(MIN_WIND_LINE_WIDTH, MAX_WIND_LINE_WIDTH);  
  47.             }  
  48.   
  49.             // needed for checking direction  
  50.             mLastAnimationTime = mLoadingAnimationTime;  
  51.         }  
  52.   
  53.         drawJet(canvas);  
  54.         drawSideClouds(canvas);  
  55.         drawCenterClouds(canvas);  
  56.   
  57.         canvas.restoreToCount(saveCount);  
  58.     }  

[java]  view plain copy print ?
  1. /** 
  2.      * Draw wind on loading animation 
  3.      * 
  4.      * @param canvas  - area where we will draw 
  5.      * @param y       - y position fot one of lines 
  6.      * @param xOffset - x offset for on of lines 
  7.      */  
  8.     private void drawWind(Canvas canvas, float y, float xOffset) {  
  9.         /* We should multiply current animation time with this coefficient for taking all screen width in time 
  10.         Removing slowing of animation with dividing on {@LINK #SLOW_DOWN_ANIMATION_COEFFICIENT} 
  11.         And we should don't forget about distance that should "fly" line that depend on screen of device and x offset 
  12.         */  
  13.         float cof = (mScreenWidth + xOffset) / (LOADING_ANIMATION_COEFFICIENT / SLOW_DOWN_ANIMATION_COEFFICIENT);  
  14.         float time = mLoadingAnimationTime;  
  15.   
  16.         // HORRIBLE HACK FOR REVERS ANIMATION THAT SHOULD WORK LIKE RESTART ANIMATION  
  17.         if (mLastAnimationTime - mLoadingAnimationTime > 0) {  
  18.             mInverseDirection = true;  
  19.             // take time from 0 to end of animation time  
  20.             time = (LOADING_ANIMATION_COEFFICIENT / SLOW_DOWN_ANIMATION_COEFFICIENT) - mLoadingAnimationTime;  
  21.         } else {  
  22.             mNewWindSet = true;  
  23.             mInverseDirection = false;  
  24.         }  
  25.   
  26.         // Taking current x position of drawing wind  
  27.         // For fully disappearing of line we should subtract wind line width  
  28.         float x = (mScreenWidth - (time * cof)) + xOffset - mWindLineWidth;  
  29.         float xEnd = x + mWindLineWidth;  
  30.   
  31.         canvas.drawLine(x, y, xEnd, y, mWindPaint);  
  32.     }  
  33.   
  34.     private void drawSideClouds(Canvas canvas) {  
  35.         Matrix matrixLeftClouds = mMatrix;  
  36.         Matrix matrixRightClouds = mAdditionalMatrix;  
  37.         matrixLeftClouds.reset();  
  38.         matrixRightClouds.reset();  
  39.   
  40.         // Drag percent will newer get more then 1 here  
  41.         float dragPercent = Math.min(1f, Math.abs(mPercent));  
  42.   
  43.         boolean overdrag = false;  
  44.   
  45.         // But we check here for overdrag  
  46.         if (mPercent > 1.0f) {  
  47.             overdrag = true;  
  48.         }  
  49.   
  50.         float scale;  
  51.         float scalePercentDelta = dragPercent - SCALE_START_PERCENT;  
  52.         if (scalePercentDelta > 0) {  
  53.             float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT);  
  54.             scale = SIDE_CLOUDS_INITIAL_SCALE + (SIDE_CLOUDS_FINAL_SCALE - SIDE_CLOUDS_INITIAL_SCALE) * scalePercent;  
  55.         } else {  
  56.             scale = SIDE_CLOUDS_INITIAL_SCALE;  
  57.         }  
  58.   
  59.         // Current y position of clouds  
  60.         float dragYOffset = mParent.getTotalDragDistance() * (1.0f - dragPercent);  
  61.   
  62.         // Position where clouds fully visible on screen and we should drag them with content of listView  
  63.         int cloudsVisiblePosition = mParent.getTotalDragDistance() / 2 - mLeftCloudsHeightCenter;  
  64.   
  65.         boolean needMoveCloudsWithContent = false;  
  66.         if (dragYOffset < cloudsVisiblePosition) {  
  67.             needMoveCloudsWithContent = true;  
  68.         }  
  69.   
  70.         float offsetRightX = mScreenWidth - mRightClouds.getWidth();  
  71.         float offsetRightY = (needMoveCloudsWithContent  
  72.                 ? mParent.getTotalDragDistance() * dragPercent - mLeftClouds.getHeight()  
  73.                 : dragYOffset)  
  74.                 + (overdrag ? mTop : 0);  
  75.   
  76.         float offsetLeftX = 0;  
  77.         float offsetLeftY = (needMoveCloudsWithContent  
  78.                 ? mParent.getTotalDragDistance() * dragPercent - mLeftClouds.getHeight()  
  79.                 : dragYOffset)  
  80.                 + (overdrag ? mTop : 0);  
  81.   
  82.         // Magic with animation on loading process  
  83.         if (isRefreshing) {  
  84.             if (checkCurrentAnimationPart(AnimationPart.FIRST)) {  
  85.                 offsetLeftY += getAnimationPartValue(AnimationPart.FIRST) / Y_SIDE_CLOUDS_SLOW_DOWN_COF;  
  86.                 offsetRightX -= getAnimationPartValue(AnimationPart.FIRST) / X_SIDE_CLOUDS_SLOW_DOWN_COF;  
  87.             } else if (checkCurrentAnimationPart(AnimationPart.SECOND)) {  
  88.                 offsetLeftY += getAnimationPartValue(AnimationPart.SECOND) / Y_SIDE_CLOUDS_SLOW_DOWN_COF;  
  89.                 offsetRightX -= getAnimationPartValue(AnimationPart.SECOND) / X_SIDE_CLOUDS_SLOW_DOWN_COF;  
  90.             } else if (checkCurrentAnimationPart(AnimationPart.THIRD)) {  
  91.                 offsetLeftY -= getAnimationPartValue(AnimationPart.THIRD) / Y_SIDE_CLOUDS_SLOW_DOWN_COF;  
  92.                 offsetRightX += getAnimationPartValue(AnimationPart.THIRD) / X_SIDE_CLOUDS_SLOW_DOWN_COF;  
  93.             } else if (checkCurrentAnimationPart(AnimationPart.FOURTH)) {  
  94.                 offsetLeftY -= getAnimationPartValue(AnimationPart.FOURTH) / X_SIDE_CLOUDS_SLOW_DOWN_COF;  
  95.                 offsetRightX += getAnimationPartValue(AnimationPart.FOURTH) / Y_SIDE_CLOUDS_SLOW_DOWN_COF;  
  96.             }  
  97.         }  
  98.   
  99.         matrixRightClouds.postScale(scale, scale, mRightCloudsWidthCenter, mRightCloudsHeightCenter);  
  100.         matrixRightClouds.postTranslate(offsetRightX, offsetRightY);  
  101.   
  102.         matrixLeftClouds.postScale(scale, scale, mLeftCloudsWidthCenter, mLeftCloudsHeightCenter);  
  103.         matrixLeftClouds.postTranslate(offsetLeftX, offsetLeftY);  
  104.   
  105.         canvas.drawBitmap(mLeftClouds, matrixLeftClouds, null);  
  106.         canvas.drawBitmap(mRightClouds, matrixRightClouds, null);  
  107.     }  
  108.   
  109.     private void drawCenterClouds(Canvas canvas) {  
  110.         Matrix matrix = mMatrix;  
  111.         matrix.reset();  
  112.         float dragPercent = Math.min(1f, Math.abs(mPercent));  
  113.   
  114.         float scale;  
  115.         float overdragPercent = 0;  
  116.         boolean overdrag = false;  
  117.   
  118.         if (mPercent > 1.0f) {  
  119.             overdrag = true;  
  120.             // Here we want know about how mach percent of over drag we done  
  121.             overdragPercent = Math.abs(1.0f - mPercent);  
  122.         }  
  123.   
  124.         float scalePercentDelta = dragPercent - SCALE_START_PERCENT;  
  125.         if (scalePercentDelta > 0) {  
  126.             float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT);  
  127.             scale = CENTER_CLOUDS_INITIAL_SCALE + (CENTER_CLOUDS_FINAL_SCALE - CENTER_CLOUDS_INITIAL_SCALE) * scalePercent;  
  128.         } else {  
  129.             scale = CENTER_CLOUDS_INITIAL_SCALE;  
  130.         }  
  131.   
  132.         float parallaxPercent = 0;  
  133.         boolean parallax = false;  
  134.         // Current y position of clouds  
  135.         float dragYOffset = mParent.getTotalDragDistance() * dragPercent;  
  136.         // Position when should start parallax scrolling  
  137.         int startParallaxHeight = mParent.getTotalDragDistance() - mFrontCloudHeightCenter;  
  138.   
  139.         if (dragYOffset > startParallaxHeight) {  
  140.             parallax = true;  
  141.             parallaxPercent = dragYOffset - startParallaxHeight;  
  142.         }  
  143.   
  144.         float offsetX = (mScreenWidth / 2) - mFrontCloudWidthCenter;  
  145.         float offsetY = dragYOffset  
  146.                 - (parallax ? mFrontCloudHeightCenter + parallaxPercent : mFrontCloudHeightCenter)  
  147.                 + (overdrag ? mTop : 0);  
  148.   
  149.         float sx = overdrag ? scale + overdragPercent / 4 : scale;  
  150.         float sy = overdrag ? scale + overdragPercent / 2 : scale;  
  151.   
  152.         if (isRefreshing && !overdrag) {  
  153.             if (checkCurrentAnimationPart(AnimationPart.FIRST)) {  
  154.                 sx = scale - (getAnimationPartValue(AnimationPart.FIRST) / LOADING_ANIMATION_COEFFICIENT) / 8;  
  155.             } else if (checkCurrentAnimationPart(AnimationPart.SECOND)) {  
  156.                 sx = scale - (getAnimationPartValue(AnimationPart.SECOND) / LOADING_ANIMATION_COEFFICIENT) / 8;  
  157.             } else if (checkCurrentAnimationPart(AnimationPart.THIRD)) {  
  158.                 sx = scale + (getAnimationPartValue(AnimationPart.THIRD) / LOADING_ANIMATION_COEFFICIENT) / 6;  
  159.             } else if (checkCurrentAnimationPart(AnimationPart.FOURTH)) {  
  160.                 sx = scale + (getAnimationPartValue(AnimationPart.FOURTH) / LOADING_ANIMATION_COEFFICIENT) / 6;  
  161.             }  
  162.             sy = sx;  
  163.         }  
  164.   
  165.         matrix.postScale(sx, sy, mFrontCloudWidthCenter, mFrontCloudHeightCenter);  
  166.         matrix.postTranslate(offsetX, offsetY);  
  167.   
  168.         canvas.drawBitmap(mFrontClouds, matrix, null);  
  169.     }  
  170.   
  171.     private void drawJet(Canvas canvas) {  
  172.         Matrix matrix = mMatrix;  
  173.         matrix.reset();  
  174.   
  175.         float dragPercent = mPercent;  
  176.         float rotateAngle = 0;  
  177.   
  178.         // Check overdrag  
  179.         if (dragPercent > 1.0f && !mEndOfRefreshing) {  
  180.             rotateAngle = (dragPercent % 1) * 10;  
  181.             dragPercent = 1.0f;  
  182.         }  
  183.   
  184.         float offsetX = ((mScreenWidth * dragPercent) / 2) - mJetWidthCenter;  
  185.   
  186.         float offsetY = mJetTopOffset  
  187.                 + (mParent.getTotalDragDistance() / 2)  
  188.                 * (1.0f - dragPercent)  
  189.                 - mJetHeightCenter;  
  190.   
  191.         if (isRefreshing) {  
  192.             if (checkCurrentAnimationPart(AnimationPart.FIRST)) {  
  193.                 offsetY -= getAnimationPartValue(AnimationPart.FIRST);  
  194.             } else if (checkCurrentAnimationPart(AnimationPart.SECOND)) {  
  195.                 offsetY -= getAnimationPartValue(AnimationPart.SECOND);  
  196.             } else if (checkCurrentAnimationPart(AnimationPart.THIRD)) {  
  197.                 offsetY += getAnimationPartValue(AnimationPart.THIRD);  
  198.             } else if (checkCurrentAnimationPart(AnimationPart.FOURTH)) {  
  199.                 offsetY += getAnimationPartValue(AnimationPart.FOURTH);  
  200.             }  
  201.         }  
  202.   
  203.         matrix.setTranslate(offsetX, offsetY);  
  204.   
  205.         if (dragPercent == 1.0f) {  
  206.             matrix.preRotate(rotateAngle, mJetWidthCenter, mJetHeightCenter);  
  207.         }  
  208.   
  209.         canvas.drawBitmap(mJet, matrix, null);  
  210.     }  

动画效果已经画好了,下面我们来看看怎么结合下拉刷新来调用吧?

二、我们还需要自定义一个PullToRefreshView(下拉刷新)

1.我们的PullToRefreshView这里需要继承ViewGroup

我们先把刚才定义的刷新时的动画加进来
[java]  view plain copy print ?
  1. private RefreshView mRefreshView;  
[java]  view plain copy print ?
  1. <pre name="code" class="java">private ImageView mRefreshImageView;  
[java]  view plain copy print ?
  1. <pre name="code" class="java">mRefreshImageView = new ImageView(context);  
  2.         mRefreshView = new RefreshView(getContext(), this);  
  3.         mRefreshImageView.setImageDrawable(mRefreshView);  
  4.   
  5.         addView(mRefreshImageView);  
 
 
 
 

2.然后我们设置OntouchEvent()事件

[java]  view plain copy print ?
  1. @Override  
  2.     public boolean onTouchEvent(@NonNull MotionEvent ev) {  
  3.   
  4.         if (!mIsBeingDragged) {  
  5.             return super.onTouchEvent(ev);  
  6.         }  
  7.   
  8.         final int action = MotionEventCompat.getActionMasked(ev);  
  9.   
  10.         switch (action) {  
  11.             case MotionEvent.ACTION_MOVE: {  
  12.                 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);  
  13.                 if (pointerIndex < 0) {  
  14.                     return false;  
  15.                 }  
  16.   
  17.                 final float y = MotionEventCompat.getY(ev, pointerIndex);  
  18.                 final float yDiff = y - mInitialMotionY;  
  19.                 final float scrollTop = yDiff * DRAG_RATE;  
  20.                 mCurrentDragPercent = scrollTop / mTotalDragDistance;  
  21.                 if (mCurrentDragPercent < 0) {  
  22.                     return false;  
  23.                 }  
  24.                 float boundedDragPercent = Math.min(1f, Math.abs(mCurrentDragPercent));  
  25.                 float extraOS = Math.abs(scrollTop) - mTotalDragDistance;  
  26.                 float slingshotDist = mTotalDragDistance;  
  27.                 float tensionSlingshotPercent = Math.max(0,  
  28.                         Math.min(extraOS, slingshotDist * 2) / slingshotDist);  
  29.                 float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(  
  30.                         (tensionSlingshotPercent / 4), 2)) * 2f;  
  31.                 float extraMove = (slingshotDist) * tensionPercent / 2;  
  32.                 int targetY = (int) ((slingshotDist * boundedDragPercent) + extraMove);  
  33.   
  34.                 mRefreshView.setPercent(mCurrentDragPercent);  
  35.                 setTargetOffsetTop(targetY - mCurrentOffsetTop, true);  
  36.                 break;  
  37.             }  
  38.             case MotionEventCompat.ACTION_POINTER_DOWN:  
  39.                 final int index = MotionEventCompat.getActionIndex(ev);  
  40.                 mActivePointerId = MotionEventCompat.getPointerId(ev, index);  
  41.                 break;  
  42.             case MotionEventCompat.ACTION_POINTER_UP:  
  43.                 onSecondaryPointerUp(ev);  
  44.                 break;  
  45.             case MotionEvent.ACTION_UP:  
  46.             case MotionEvent.ACTION_CANCEL: {  
  47.                 if (mActivePointerId == INVALID_POINTER) {  
  48.                     return false;  
  49.                 }  
  50.                 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);  
  51.                 final float y = MotionEventCompat.getY(ev, pointerIndex);  
  52.                 final float overScrollTop = (y - mInitialMotionY) * DRAG_RATE;  
  53.                 mIsBeingDragged = false;  
  54.                 if (overScrollTop > mTotalDragDistance) {  
  55.                     setRefreshing(truetrue);  
  56.                 } else {  
  57.                     mRefreshing = false;  
  58.                     animateOffsetToPosition(mAnimateToStartPosition);  
  59.                 }  
  60.                 mActivePointerId = INVALID_POINTER;  
  61.                 return false;  
  62.             }  
  63.         }  
  64.   
  65.         return true;  
  66.     }  

三、最后我们看怎样在Activity中使用这个下拉刷新控件

1.先看一下布局文件

这里是我们的下拉刷新空间嵌套着我们的ListView,然后我们再给ListView填充数据即可
[java]  view plain copy print ?
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     tools:context=".PullToRefreshActivity">  
  6.   
  7.     <com.hankkin.AnimationPullToRefreshDemo.PullToRefreshView  
  8.         android:id="@+id/pull_to_refresh"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="match_parent">  
  11.   
  12.         <ListView  
  13.             android:id="@+id/list_view"  
  14.             android:divider="@null"  
  15.             android:dividerHeight="0dp"  
  16.             android:fadingEdge="none"  
  17.             android:layout_width="match_parent"  
  18.             android:layout_height="match_parent" />  
  19.   
  20.     </com.hankkin.AnimationPullToRefreshDemo.PullToRefreshView>  
  21.   
  22. </RelativeLayout>  

2.为ListView填充数据

为了我们的效果比较好看,这里我们给ListView的每一个item填充不同的颜色,看起来会比较高大上。
[java]  view plain copy print ?
  1. Map<String, Integer> map;  
  2.         List<Map<String, Integer>> sampleList = new ArrayList<Map<String, Integer>>();  
  3.   
  4.   
  5.         int[] colors = {  
  6.                 R.color.saffron,  
  7.                 R.color.eggplant,  
  8.                 R.color.sienna};  
  9.   
  10.         int[] tripNames = {  
  11.                 R.string.trip_to_india,  
  12.                 R.string.trip_to_italy,  
  13.                 R.string.trip_to_indonesia};  
  14.   
  15.         for (int i = 0; i < tripNames.length; i++) {  
  16.             map = new HashMap<String, Integer>();  
  17.             map.put(SampleAdapter.KEY_NAME, tripNames[i]);  
  18.             map.put(SampleAdapter.KEY_COLOR, colors[i]);  
  19.             sampleList.add(map);  
  20.         }  
  21.   
  22.         ListView listView = (ListView) findViewById(R.id.list_view);  
  23.         listView.setAdapter(new SampleAdapter(this, R.layout.list_item, sampleList));  

3.最后,我们再设置一下下拉刷新的监听事件就OK了

[java]  view plain copy print ?
  1. mPullToRefreshView = (PullToRefreshView) findViewById(R.id.pull_to_refresh);  
  2.         mPullToRefreshView.setOnRefreshListener(new PullToRefreshView.OnRefreshListener() {  
  3.             @Override  
  4.             public void onRefresh() {  
  5.                 mPullToRefreshView.postDelayed(new Runnable() {  
  6.                     @Override  
  7.                     public void run() {  
  8.                         mPullToRefreshView.setRefreshing(false);  
  9.                     }  
  10.                 }, REFRESH_DELAY);  
  11.             }  
  12.         });  
怎么样?有没有很高大上啊?
说明:
自定义View里面的一些动画效果,包括飞机的动画效果,风的动画效果和一些方法没有详细介绍,有兴趣的小伙伴可以到github上下载源码仔细研究一下,作者写的还是比较不错的,很佩服。如果一些小伙伴还没有用惯AndroidStudio,这里也有Idea版本的,用Eclise同样可以打开运行看效果的。
下载地址:

http://www.eoeandroid.com/thread-905093-1-1.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值