Android Path Time ScrollBar(Path 时间轴)

简介:
版本号:1.0
日期:2014.4.22
版权:© 2014 kince 转载注明出处

  这是仿Path2.0UI的一个demo的截图,我最早是在农民伯伯的这篇博客中看到的 【Andorid X 项目笔记】开源项目使用(6) ,他说这个程序是反编译Path的。可是这次我特地看了一下代码,发现事实上不是这种。原帖地址应该是这个: http://www.eoeandroid.com/forum.php?mod=viewthread&tid=187725 ,作者使用的是github上的一个开源项目: Android-ScrollBarPanel ,它实现的效果例如以下:


  已经非常接近Path的效果了。还有墨迹天气的实景也是使用了这种效果:

  并且,墨迹天气用的也是这个开源项目。效果什么基本都没改。所以以下重点说一下这个开源项目的实现。

在看它的代码之前先来分析一下这个效果该怎样实现,它就是在滚动栏(scrollbar)的旁边动态显示一个View。这个View里面显示的内容会随着滚动栏的位置变化而变化。一般像带滑动效果的容器控制都会有滚动栏,比方ScrollView、ListView、GeidView等。那这个滚动栏究竟是什么呢?它是一个View的属性,该属性是继承view的, 目的设置滚动栏显示。有以下设置none(隐藏)。horizontal(水平),vertical (垂直)。并非全部的view设置就有效果。 LinearLayout 设置也没有效果。 要想在超过一屏时拖动效果,在最外层加上ScrollView。并且能够自己定义滚动栏的样式和位置。但Path用的并非自己定义的滚动栏,它是在滚动栏旁边加的View。如图:


   若是在滚动栏的旁边加入显示View,首先须要获取滚动栏的位置,当在滑动的时候在显示滚动栏的同一时候也让加入的View显示出来,也就是说它和滚动栏的显示是同步的。

那究竟怎样实现呢。带着这些疑问看一下源代码:

package com.dafruits.android.library.widgets;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListView;

import com.dafruits.android.library.R;

public class ExtendedListView extends ListView implements OnScrollListener {

     public static interface OnPositionChangedListener {

          public void onPositionChanged(ExtendedListView listView, int position, View scrollBarPanel);

     }

     private OnScrollListener mOnScrollListener = null;

     private View mScrollBarPanel = null;
     private int mScrollBarPanelPosition = 0;

     private OnPositionChangedListener mPositionChangedListener;
     private int mLastPosition = -1;

     private Animation mInAnimation = null;
     private Animation mOutAnimation = null;

     private final Handler mHandler = new Handler();

     private final Runnable mScrollBarPanelFadeRunnable = new Runnable() {

          @Override
          public void run() {
               if (mOutAnimation != null) {
                    mScrollBarPanel.startAnimation(mOutAnimation);
               }
          }
     };

     /*
     * keep track of Measure Spec
     */
     private int mWidthMeasureSpec;
     private int mHeightMeasureSpec;

     public ExtendedListView(Context context) {
          this(context, null);
     }

     public ExtendedListView(Context context, AttributeSet attrs) {
          this(context, attrs, android.R.attr.listViewStyle);
     }

     public ExtendedListView(Context context, AttributeSet attrs, int defStyle) {
          super(context, attrs, defStyle);

          super.setOnScrollListener(this);

          final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ExtendedListView);
          final int scrollBarPanelLayoutId = a.getResourceId(R.styleable.ExtendedListView_scrollBarPanel, -1);
          final int scrollBarPanelInAnimation = a.getResourceId(R.styleable.ExtendedListView_scrollBarPanelInAnimation, R.anim.in_animation);
          final int scrollBarPanelOutAnimation = a.getResourceId(R.styleable.ExtendedListView_scrollBarPanelOutAnimation, R.anim.out_animation);
          a.recycle();

          if (scrollBarPanelLayoutId != -1) {
               setScrollBarPanel(scrollBarPanelLayoutId);
          }

          final int scrollBarPanelFadeDuration = ViewConfiguration.getScrollBarFadeDuration();

          if (scrollBarPanelInAnimation > 0) {
               mInAnimation = AnimationUtils.loadAnimation(getContext(), scrollBarPanelInAnimation);
          }
         
          if (scrollBarPanelOutAnimation > 0) {
               mOutAnimation = AnimationUtils.loadAnimation(getContext(), scrollBarPanelOutAnimation);
               mOutAnimation.setDuration(scrollBarPanelFadeDuration);

               mOutAnimation.setAnimationListener(new AnimationListener() {

                    @Override
                    public void onAnimationStart(Animation animation) {
                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {

                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {
                         if (mScrollBarPanel != null) {
                              mScrollBarPanel.setVisibility(View.GONE);
                         }
                    }
               });
          }
     }

     @Override
     public void onScrollStateChanged(AbsListView view, int scrollState) {
          if (mOnScrollListener != null) {
               mOnScrollListener.onScrollStateChanged(view, scrollState);
          }
     }

     @Override
     public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
          if (null != mPositionChangedListener && null != mScrollBarPanel) {

               // Don't do anything if there is no itemviews
               if (totalItemCount > 0) {
                    /*
                    * from android source code (ScrollBarDrawable.java)
                    */
                    final int thickness = getVerticalScrollbarWidth();
                    int height = Math.round((float) getMeasuredHeight() * computeVerticalScrollExtent() / computeVerticalScrollRange());
                    int thumbOffset = Math.round((float) (getMeasuredHeight() - height) * computeVerticalScrollOffset() / (computeVerticalScrollRange() - computeVerticalScrollExtent()));
                    final int minLength = thickness * 2;
                    if (height < minLength) {
                         height = minLength;
                    }
                    thumbOffset += height / 2;
                   
                    /*
                    * find out which itemviews the center of thumb is on
                    */
                    final int count = getChildCount();
                    for (int i = 0; i < count; ++i) {
                         final View childView = getChildAt(i);
                         if (childView != null) {
                              if (thumbOffset > childView.getTop() && thumbOffset < childView.getBottom()) {
                                   /*
                                   * we have our candidate
                                   */
                                   if (mLastPosition != firstVisibleItem + i) {
                                        mLastPosition = firstVisibleItem + i;
                                       
                                        /*
                                        * inform the position of the panel has changed
                                        */
                                        mPositionChangedListener.onPositionChanged(this, mLastPosition, mScrollBarPanel);
                                       
                                        /*
                                        * measure panel right now since it has just changed
                                        *
                                        * INFO: quick hack to handle TextView has ScrollBarPanel (to wrap text in
                                        * case TextView's content has changed)
                                        */
                                        measureChild(mScrollBarPanel, mWidthMeasureSpec, mHeightMeasureSpec);
                                   }
                                   break;
                              }
                         }
                    }

                    /*
                    * update panel position
                    */
                    mScrollBarPanelPosition = thumbOffset - mScrollBarPanel.getMeasuredHeight() / 2;
                    final int x = getMeasuredWidth() - mScrollBarPanel.getMeasuredWidth() - getVerticalScrollbarWidth();
                    mScrollBarPanel.layout(x, mScrollBarPanelPosition, x + mScrollBarPanel.getMeasuredWidth(),
                              mScrollBarPanelPosition + mScrollBarPanel.getMeasuredHeight());
               }
          }

          if (mOnScrollListener != null) {
               mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
          }
     }

     public void setOnPositionChangedListener(OnPositionChangedListener onPositionChangedListener) {
          mPositionChangedListener = onPositionChangedListener;
     }

     @Override
     public void setOnScrollListener(OnScrollListener onScrollListener) {
          mOnScrollListener = onScrollListener;
     }

     public void setScrollBarPanel(View scrollBarPanel) {
          mScrollBarPanel = scrollBarPanel;
          mScrollBarPanel.setVisibility(View.GONE);
          requestLayout();
     }

     public void setScrollBarPanel(int resId) {
          setScrollBarPanel(LayoutInflater.from(getContext()).inflate(resId, this, false));
     }

     public View getScrollBarPanel() {
          return mScrollBarPanel;
     }
    
     @Override
     protected boolean awakenScrollBars(int startDelay, boolean invalidate) {
          final boolean isAnimationPlayed = super.awakenScrollBars(startDelay, invalidate);
         
          if (isAnimationPlayed == true && mScrollBarPanel != null) {
               if (mScrollBarPanel.getVisibility() == View.GONE) {
                    mScrollBarPanel.setVisibility(View.VISIBLE);
                    if (mInAnimation != null) {
                         mScrollBarPanel.startAnimation(mInAnimation);
                    }
               }
              
               mHandler.removeCallbacks(mScrollBarPanelFadeRunnable);
               mHandler.postAtTime(mScrollBarPanelFadeRunnable, AnimationUtils.currentAnimationTimeMillis() + startDelay);
          }

          return isAnimationPlayed;
     }

     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          super.onMeasure(widthMeasureSpec, heightMeasureSpec);

          if (mScrollBarPanel != null && getAdapter() != null) {
               mWidthMeasureSpec = widthMeasureSpec;
               mHeightMeasureSpec = heightMeasureSpec;
               measureChild(mScrollBarPanel, widthMeasureSpec, heightMeasureSpec);
          }
     }

     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
          super.onLayout(changed, left, top, right, bottom);

          if (mScrollBarPanel != null) {
               final int x = getMeasuredWidth() - mScrollBarPanel.getMeasuredWidth() - getVerticalScrollbarWidth();
               mScrollBarPanel.layout(x, mScrollBarPanelPosition, x + mScrollBarPanel.getMeasuredWidth(),
                         mScrollBarPanelPosition + mScrollBarPanel.getMeasuredHeight());
          }
     }

     @Override
     protected void dispatchDraw(Canvas canvas) {
          super.dispatchDraw(canvas);

          if (mScrollBarPanel != null && mScrollBarPanel.getVisibility() == View.VISIBLE) {
               drawChild(canvas, mScrollBarPanel, getDrawingTime());
          }
     }

     @Override
     public void onDetachedFromWindow() {
          super.onDetachedFromWindow();

          mHandler.removeCallbacks(mScrollBarPanelFadeRunnable);
     }
}
   通过阅读源代码发现,这是一个自己定义的ListView,并且继承了OnScrollListener接口。这个接口是在AbsListView.java里面定义的。主要是负责滑动事件的处理,它的代码例如以下:
 /**
     * Interface definition for a callback to be invoked when the list or grid
     * has been scrolled.
     */
    public interface OnScrollListener {

        /**
         * The view is not scrolling. Note navigating the list using the trackball counts as
         * being in the idle state since these transitions are not animated.
         */
        public static int SCROLL_STATE_IDLE = 0;

        /**
         * The user is scrolling using touch, and their finger is still on the screen
         */
        public static int SCROLL_STATE_TOUCH_SCROLL = 1;

        /**
         * The user had previously been scrolling using touch and had performed a fling. The
         * animation is now coasting to a stop
         */
        public static int SCROLL_STATE_FLING = 2;

        /**
         * Callback method to be invoked while the list view or grid view is being scrolled. If the
         * view is being scrolled, this method will be called before the next frame of the scroll is
         * rendered. In particular, it will be called before any calls to
         * {@link Adapter#getView(int, View, ViewGroup)}.
         *
         * @param view The view whose scroll state is being reported
         *
         * @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE},
         * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
         */
        public void onScrollStateChanged(AbsListView view, int scrollState);

        /**
         * Callback method to be invoked when the list or grid has been scrolled. This will be
         * called after the scroll has completed
         * @param view The view whose scroll state is being reported
         * @param firstVisibleItem the index of the first visible cell (ignore if
         *        visibleItemCount == 0)
         * @param visibleItemCount the number of visible cells
         * @param totalItemCount the number of items in the list adaptor
         */
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                int totalItemCount);
    }
   OnScrollListener定义了三个常量。分别表示当屏幕停止滚动时为0;当屏幕滚动且用户使用的触碰或手指还在屏幕上时为1;由于用户的操作。屏幕产生惯性滑动时为2。详细解释例如以下:
new OnScrollListener() {   
        boolean isLastRow = false;   
       
        @Override   
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {   
            //滚动时一直回调,直到停止滚动时才停止回调。单击时回调一次。   
            //firstVisibleItem:当前能看见的第一个列表项ID(从0開始)   
            //visibleItemCount:当前能看见的列表项个数(小半个也算)   
            //totalItemCount:列表项共数   
       
            //推断是否滚到最后一行   
            if (firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount > 0) {   
                isLastRow = true;   
            }   
        }   
        @Override   
        public void onScrollStateChanged(AbsListView view, int scrollState) {   
            //正在滚动时回调,回调2-3次,手指没抛则回调2次。

scrollState = 2的这次不回调 //回调顺序例如以下 //第1次:scrollState = SCROLL_STATE_TOUCH_SCROLL(1) 正在滚动 //第2次:scrollState = SCROLL_STATE_FLING(2) 手指做了抛的动作(手指离开屏幕前,用力滑了一下) //第3次:scrollState = SCROLL_STATE_IDLE(0) 停止滚动 //当屏幕停止滚动时为0;当屏幕滚动且用户使用的触碰或手指还在屏幕上时为1。 //由于用户的操作,屏幕产生惯性滑动时为2 //当滚到最后一行且停止滚动时,运行载入 if (isLastRow && scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { //载入元素 ...... isLastRow = false; } } }

   了解完OnScrollListener这个接口再回头看一下代码,首先定义了一个回调:
   public static interface OnPositionChangedListener {

          public void onPositionChanged(ExtendedListView listView, int position,
                    View scrollBarPanel);

     }
  这个用来在Activity中设置监听事件的,Activity的代码例如以下:
package com.dafruits.android.samples;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.dafruits.android.library.widgets.ExtendedListView;
import com.dafruits.android.library.widgets.ExtendedListView.OnPositionChangedListener;

public class DemoScrollBarPanelActivity extends Activity implements OnPositionChangedListener {

     private ExtendedListView mListView;

     @Override
     public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);

          setContentView(R.layout.main);

          mListView = (ExtendedListView) findViewById(android.R.id.list);
          mListView.setAdapter(new DummyAdapter());
          mListView.setCacheColorHint(Color.TRANSPARENT);
          mListView.setOnPositionChangedListener(this);
     }

     private class DummyAdapter extends BaseAdapter {

          private int mNumDummies = 100;

          @Override
          public int getCount() {
               return mNumDummies;
          }

          @Override
          public Object getItem(int position) {
               return position;
          }

          @Override
          public long getItemId(int position) {
               return position;
          }

          @Override
          public View getView(int position, View convertView, ViewGroup parent) {
               if (convertView == null) {
                    convertView = LayoutInflater.from(DemoScrollBarPanelActivity.this).inflate(R.layout.list_item, parent,
                              false);
               }

               TextView textView = (TextView) convertView;
               textView.setText("" + position);

               return convertView;
          }
     }

     @Override
     public void onPositionChanged(ExtendedListView listView, int firstVisiblePosition, View scrollBarPanel) {
          ((TextView) scrollBarPanel).setText("Position " + firstVisiblePosition);
     }
}
  接着看一下第三个构造方法,由于这个自己定义的ListView定义了自己的属性。所以须要从attrs文件里来取出这些属性。自己定义的属性包含三个部分,一是在ListView滑动时弹出的View,二是这个View弹出时的动画,三是这个View消失时的动画。然后開始设置这个弹出的View:
  if (scrollBarPanelLayoutId != -1) {
               setScrollBarPanel(scrollBarPanelLayoutId);
          }
   看一下设置的方法。
  public void setScrollBarPanel(View scrollBarPanel) {
          mScrollBarPanel = scrollBarPanel;
          mScrollBarPanel.setVisibility(View.GONE);
          requestLayout();
     }

     public void setScrollBarPanel(int resId) {
          setScrollBarPanel(LayoutInflater.from(getContext()).inflate(resId,
                    this, false));
     }
  
  先是调用以下这种方法。从xml文件里载入弹出View的布局,在这个地方须要说一下假设自己定义的View不须要手动绘制的话,那么就能够使用LayoutInflater去在xml中载入一个已经配置好的视图,本例中就是使用这个方式。这样mScrollBarPanel就储存了弹出的View。然后设置为不可见。使用requestLayout()刷新一下视图。再接着就是载入两个弹出的动画。特别的。在mOutAnimation动画中设置了监听器,在动画结束的时候设置弹出的View不可见。

  回到第三个构造方法中,在第二行设置了super.setOnScrollListener(this),这种方法是效果实现的关键。为什么这么说。先看一下它的源代码。

它是在AbsListView中定义的。

   /**
     * Set the listener that will receive notifications every time the list scrolls.
     *
     * @param l the scroll listener
     */
    public void setOnScrollListener(OnScrollListener l) {
        mOnScrollListener = l;
        invokeOnItemScrollListener();
    }
  设置这种方法后,会传递一个 OnScrollListener对象给 mOnScrollListener,然后调用 invokeOnItemScrollListener()方法,它的代码例如以下:
 /**
     * Notify our scroll listener (if there is one) of a change in scroll state
     */
    void invokeOnItemScrollListener() {
        if (mFastScroller != null) {
            mFastScroller.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
        }
        if (mOnScrollListener != null) {
            mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
        }
        onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
    }
   假设mOnScrollListener不为空的话,就调用mOnScrollListener的onScroll方法。而onScroll方法正是OnScrollListener接口定义的抽象方法,由于我们在ListView中继承了OnScrollListener接口,重载了onScroll方法,所以将会调用我们自己实现的onScroll方法。就是这样一个流程。
  然后看一下onScroll方法的实现,
@Override
     public void onScroll(AbsListView view, int firstVisibleItem,
               int visibleItemCount, int totalItemCount) {
          Log.i("onScroll", "onScroll");
          if (null != mPositionChangedListener && null != mScrollBarPanel) {

               // Don't do anything if there is no itemviews
               if (totalItemCount > 0) {
                    /*
                    * from android source code (ScrollBarDrawable.java)
                    */
                    final int thickness = getVerticalScrollbarWidth();
                    int height = Math.round((float) getMeasuredHeight()
                              * computeVerticalScrollExtent()
                              / computeVerticalScrollRange());
                    int thumbOffset = Math
                              .round((float) (getMeasuredHeight() - height)
                                        * computeVerticalScrollOffset()
                                        / (computeVerticalScrollRange() - computeVerticalScrollExtent()));
                    final int minLength = thickness * 2;
                    if (height < minLength) {
                         height = minLength;
                    }
                    thumbOffset += height / 2;

                    /*
                    * find out which itemviews the center of thumb is on
                    */
                    final int count = getChildCount();
                    for (int i = 0; i < count; ++i) {
                         final View childView = getChildAt(i);
                         if (childView != null) {
                              if (thumbOffset > childView.getTop()
                                        && thumbOffset < childView.getBottom()) {
                                   /*
                                   * we have our candidate
                                   */
                                   if (mLastPosition != firstVisibleItem + i) {
                                        mLastPosition = firstVisibleItem + i;

                                        /*
                                        * inform the position of the panel has changed
                                        */
                                        mPositionChangedListener.onPositionChanged(
                                                  this, mLastPosition, mScrollBarPanel);

                                        /*
                                        * measure panel right now since it has just
                                        * changed
                                        * 
                                        * INFO: quick hack to handle TextView has
                                        * ScrollBarPanel (to wrap text in case
                                        * TextView's content has changed)
                                        */
                                        measureChild(mScrollBarPanel,
                                                  mWidthMeasureSpec, mHeightMeasureSpec);
                                   }
                                   break;
                              }
                         }
                    }

                    /*
                    * update panel position
                    */
                    mScrollBarPanelPosition = thumbOffset
                              - mScrollBarPanel.getMeasuredHeight() / 2;
                    final int x = getMeasuredWidth()
                              - mScrollBarPanel.getMeasuredWidth()
                              - getVerticalScrollbarWidth();
                    mScrollBarPanel.layout(
                              x,
                              mScrollBarPanelPosition,
                              x + mScrollBarPanel.getMeasuredWidth(),
                              mScrollBarPanelPosition
                                        + mScrollBarPanel.getMeasuredHeight());
               }
          }

          if (mOnScrollListener != null) {
               mOnScrollListener.onScroll(view, firstVisibleItem,
                         visibleItemCount, totalItemCount);
          }
     }
  上面已经说到。这个onScroll是随着滑动而一直调用的,而我们的需求就是在滑动的时候弹出一个View来。所以这种方法正是处理问题的关键位置。能够在这里绘制弹出View的视图。从上面的代码也能够看出,就是在这里进行弹出View大小的计算以及位置的设定等。
   最后就是之前说的自己定义ViewGroup的问题了。重载onMeasure()、onLayout()、ondispatchDraw()方法了,这个在本例中也是有所体现的,只是都比較简单,相信都看得懂。

可是这几个方法都是在View初始化的时候调用的,并且仅仅是调用一次。这样并不适合动态的绘制视图。所以这也是为什么本样例继承了OnScrollListener,然后在其onScroll方法中去绘制视图。由于onScroll方法在滑动的时候会调用,所以在滑动的时候就会绘制视图了。

因此也能够看出本例採用的是动态画图的方式,不是显示隐藏的方式。

   











本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5396685.html,如需转载请自行联系原作者
相关文章
|
Android开发
Android > Project with path ‘:audiovisualize‘ could not be found in project ‘:app‘. 异常解决方案
Android > Project with path ‘:audiovisualize‘ could not be found in project ‘:app‘. 异常解决方案
89 0
|
前端开发 Android开发
Android Path测量工具:PathMeasure
Android Path测量工具:PathMeasure
203 0
|
前端开发 Android开发
Android Canvas之Path操作
Android Canvas之Path操作
276 0
|
Android开发 数据格式 JSON
android报错 Expected BEGIN_OBJECT but was STRING at line 1 column 39 path $
      我在使用retrofit和Gson配合时,出现了这个问题,疑惑中乱七八糟瞎搞了一个下午没有解决。期间怀疑Gson解析不能使用泛型(因为我的解析使用了泛型),后来又觉得可能是我的关键字正好是解析器的某个关键字导致的异常,也打算过自定义Gson的解析过程,其实这些都不是。         第二天才搞明白,真正的问题是我的数据结构有问题,或者说我的解析出现了问题。  
4348 0
|
算法 开发工具 Android开发
android自定义控件-Path的进阶使用方法
android自定义控件-Path的进阶使用方法
363 0
android自定义控件-Path的进阶使用方法
|
开发工具 Android开发
Android Studio 报错Emulator: PANIC: Cannot find AVD system path. Please define ANDROID_SDK_ROOT(解决方案)
Android Studio 报错Emulator: PANIC: Cannot find AVD system path. Please define ANDROID_SDK_ROOT(解决方案)
Android Studio 报错Emulator: PANIC: Cannot find AVD system path. Please define ANDROID_SDK_ROOT(解决方案)
|
Android开发
【Android菜鸟学习之路】环境搭建问题-修改AVD Path
【Android菜鸟学习之路】环境搭建问题-修改AVD Path
114 0
【Android菜鸟学习之路】环境搭建问题-修改AVD Path
|
Java 开发工具 Android开发
【错误记录】Android NDK 错误排查记录 ( Could not get version from cmake.dir path ‘xxx\cmake\3.6.4111459‘. )
【错误记录】Android NDK 错误排查记录 ( Could not get version from cmake.dir path ‘xxx\cmake\3.6.4111459‘. )
415 0
【错误记录】Android NDK 错误排查记录 ( Could not get version from cmake.dir path ‘xxx\cmake\3.6.4111459‘. )
|
Android开发
Android绘制(三):Path结合属性动画, 让图标动起来!
Android绘制(一):来用shape绘出想要的图形吧!Android绘制(二):来用Path绘出想要的图形吧! 目录 效果图 前言 绘制 属性动画 最后 效果图 不废话, 直接上效果图, 感兴趣再看下去.
1352 0
|
Android开发
Android绘制(二):来用Path绘出想要的图形吧!
Android绘制(一):来用shape绘出想要的图形吧! 目录 前言 绘制线 绘制图形 绘制弧 绘制文字 组合 贝塞尔曲线 最后 前言 之前有一篇用shape进行绘制的, 但是那个偏向静态, path结合属性动画可以动起来哦~ path是什么...
1485 0