Android-自定义View实现轮播图

主要步骤
1.自定义View的实现过程(测量、布局、绘制)
2.事件拦截机制方法、图片轮播时的Scroller对象使用
3.轮播图自动轮播时,Timer、TimerTask、Handler三者的结合
4.自定义l轮播图底部圆点布局实现
主要思路
  • 1.我们需要自定义一个继承自FrameLayout的布局,利用FrameLayout布-局的特性(在同一位置放置不同的view最终显示的是最后一个View),我们就可以实现底部圆点的布局。
  • 2.我们需要准素材,就是底部素材,我们可以利用Drawable的功能 去实现一个圆点图片的展示
  • 3.我们就需要继承FrameLayout 来自定义一个类,在该类的实现过程中,我们去加载我们刚才自定义的ImageBarnnerViewGroup核心类和我们需要的布局LinearLayout来实现。
1.自定义View的实现过程(测量、布局、绘制)

测量:要实现的是一个viewGroup的容器,那么 我们就应该需要知道该容器中的子视图 ,我们要想测量我们的ViewGroup的宽度和高度,那么我们就必须先要测量子视图*的宽度和高度之和,才能知道我们的ViewGroup的宽度和高度是多少 。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    /**
     * 由于我们要实现的是一个viewGroup的容器,那么
     * 我们就应该需要知道该容器中的子视图
     * 我们要想测量我们的ViewGroup的宽度和高度,那么我们就必须先要测量子视图
     * 的宽度和高度之和,才能知道我们的ViewGroup的宽度和高度是多少
     */
    //1.求出子视图的个数
     children = getChildCount(); //我们就可以知道子视图的个数
    if(0 == children) {
        setMeasuredDimension(0, 0);
    } else {
        //2.测量子视图的宽度和高度
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        //此时我们一第一个子视图为基础,也就是说我们的ViewGroup的高度就是我们第一个子视图的高度
        //宽度就是我们第一个子视图的宽度*子视图的个数
        View view = getChildAt(0); //因为此时第一个视图绝对是存在的
        
        //3.根据子视图的宽度和高度,来求出该ViewGroup的宽度和高度
        childheight = view.getMeasuredHeight();
        childwidth =  view.getMeasuredWidth();
        int childwidth = view.getMeasuredWidth() * children; //宽度是我们所有字视图的宽度的总和
        setMeasuredDimension(childwidth, childheight);
    }
}

布局:继承ViewGroup必须要实现布局onLayout方法。其中change 代表的时候是当我们的ViewGroup布局位置发生改变的为true 没有发生改变为false

protected void onLayout(boolean change, int l, int t, int r, int b) {
       if(change) {
           int leftMargin = 0;
           for(int i = 0; i < children; i++) {
               View view = getChildAt(i); //拿出每个视图
               view.layout(leftMargin, 0, leftMargin + childwidth, childheight);
               leftMargin +=childwidth;
        }
    }
}

绘制:对于绘制来说,因为我们是自定义的ViewGroup容器 针对于容器的绘制,其实就是容器内的子控件的绘制过程,那么我们只需要 调用系统自带的绘制即可 也就说,对于viewGroup绘制过程不需要再重写该方法调用系统自带即可。

2.事件拦截机制方法、图片轮播时的Scroller对象使用

事件拦截机制方法:调用容器的拦截方法onInterceptTouchEvent。针对于该方法我们可以理解为 如果说该方法的返回值为trued的时候,那么我们自定义的ViewGroup容器就会处理此次拦截事件。 如果说 返回值为false的时候,那么我们自定义的ViewGroup 容器将不会接受此次事件的处理过程,将会继续向下传递该事件, 针对于我们自定义的ViewGroup 我们当然是希望我们的ViewGroup 容器处理接受事件 那么我们的返回值就是true。 如果返回true的话 真正处理该事件的方法 是onTouch方法。

public boolean onInterceptTouchEvent(MotionEvent ev) {
    return true;
}

事件处理过程:用2中方式 来实现 轮播图的手动 轮播

  • 1.利用scrollTo scrollBy完成轮播图的手动轮播

  • 2.利用Scroller 对象 完成轮播图的手动轮播
    事件处理思路:

  • 第一:我们在滑动屏幕图片的过程中,其实就是我们自定义ViewGroup 的子视图的移动过程,那么我们只需要知道滑动之前的横坐标和滑动之后的横坐标,此时 我们就可以此过程中移动的距离,我们在利用scrollBy方法实现图片的滑动 所以 此时我们需要2个值 是需要我们 求出的: 移动之前,移动之后的 横坐标值。

  • 第二:当我们第一次 按下得那一瞬间, 此时的移动之前和移动之后的值是相等的,也就是我们此时按下那一瞬间得哪一个点的横坐标的值。

  • 第三:我们在不断的滑动过程中,是会不断地调用我们ACTION_MOVE方法,那么此时我们就应该将 移动之前的值 和移动之后的进行保存。 此时我们能够算出滑动的距离

  • 第四: 在我们抬起那一瞬间 我们需要计算出我们此时将要滑动到那张图片的位置上。 我们此时就需要求得出将要滑动到的那张图片的索引值,(我们当前ViewGroup的滑动位置 +我们的每一张图片的宽度/2)/我们的每一张图片的宽度
    此时我们就可以利用scrollTo方法滑动到该图片的位置上。

    public boolean onTouchEvent(MotionEvent event) {
      switch(event.getAction()) {
      case MotionEvent.ACTION_DOWN:
          stopAuto();
          if(!scroller.isFinished()) {
              scroller.abortAnimation();
          }
          isClick = true;
          x = (int) event.getX();
          break;
      case MotionEvent.ACTION_MOVE:
          int moveX = (int) event.getX();
          int distance = moveX - x;
          scrollBy(-distance, 0);
          x = moveX;
          isClick = false;
          break;
      case MotionEvent.ACTION_UP:
          int scrollX = getScrollX(); //当前ViewGroup的滑动位置 
          index = (scrollX + childwidth/2)/childwidth;
          
          if(index < 0) { //说明了此事已经滑动到了最左边第一张图片
              index = 0;
          } else if (index > children -1) { //说明了此事已经滑动到了最右最后一张图片
              index = children -1;
          }
          
          if(isClick) {
              listener.clickImageIndex(index);
          } else {
              int dx = index * childwidth - scrollX; //当前滑动的距离
              //scrollTo(index*childwidth, 0);
              scroller.startScroll(scrollX, 0, dx, 0);
              postInvalidate(); 
              barnnerViewGroupListener.selectImage(index);
          }
          startAuto();
          
          break;
      default:
          break;
      }
      return true; //返回true 的目的是告诉
    

图片轮播时的Scroller对象使用:在computeScroll()方面里面,在滑动结束的时候移动到当前滑动抬起位置。
public void computeScroll() {
super.computeScroll();
if(scroller.computeScrollOffset()) { //当前scroller对象是否已经滑动完毕
scrollTo(scroller.getCurrX(), 0);
invalidate();
}
}
并且在MotionEvent.ACTION_UP实现滑动:
case MotionEvent.ACTION_UP:
int scrollX = getScrollX(); //当前ViewGroup的滑动位置
index = (scrollX + childwidth/2)/childwidth;

        if(index < 0) { //说明了此事已经滑动到了最左边第一张图片
            index = 0;
        } else if (index > children -1) { //说明了此事已经滑动到了最右最后一张图片
            index = children -1;
        }
        
        if(isClick) {
            listener.clickImageIndex(index);
        } else {
            int dx = index * childwidth - scrollX; //当前滑动的距离
            //scrollTo(index*childwidth, 0);
            scroller.startScroll(scrollX, 0, dx, 0);
            postInvalidate(); 
            barnnerViewGroupListener.selectImage(index);
        }
        startAuto();
        break;
3.轮播图自动轮播时,Timer、TimerTask、Handler三者的结合

采用Timer,TimerTask,Handler三者结合的方式来实现自动轮播我们会抽出2个方法来控制,是否启动自动轮播,我们称之为startAuto(),stopAuto();在2个方法控制过程中,实际上希望控制的是自动开启轮播图的开关。那么就需要一个变量参数 来作为自动开启轮播图的开关,我们称之为isAuto boolean true代表开启 false代表关闭。
private boolean isAuto = true; //默认情况下我们是开启自动轮播
private Timer timer = new Timer();
private TimerTask task;
private Handler autoHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch(msg.what) {
case 0: //此时我们需要图片的自动轮播
if(++index >= children) { //说明我们 此时如果是最后一张图片的话,那么我们将会从第一章图片开始重新滑动
index = 0;
}

            scrollTo(childwidth * index, 0);
            barnnerViewGroupListener.selectImage(index);
            break;
        }
        
    };
  };

  private void startAuto() {
      isAuto = true;
   }

  private void stopAuto() {
      isAuto = false;
  }

  private void initobj() {
      scroller = new Scroller(getContext());
    
      task = new TimerTask() {
        public void run() {
            if(isAuto) { //开启轮播图
                autoHandler.sendEmptyMessage(0);
            }
        }
    };
    
    timer.schedule(task, 100,1000);
  }

实现图片轮播图的核心的ImageBarnnerViewGroup类:
public class ImageBarnnerViewGroup extends ViewGroup{
private int children; //我们viewGroup子视图的总个数
private int childheight; //子视图的高度
private int childwidth; //子视图的宽度

      private int x; //此时的X的值,代表的是第一次按下的位置横坐标 ,每一次移动过程中 移动之前的位置横坐标
      private int index; //代表的是我们每一张图片的索引

      private Scroller scroller;

        /**
         * 要想实现图片的单机事件的获取
         * 我们采用的方法 就是利用一个单击变量开关进行判断 , 再用户离开屏幕的一瞬间我们去判断变量开关来判断用户的操作是点击 还 是移动
         */
      private boolean isClick; //true 点击事件  false 移动
      private ImageBarnnerListener listener;

      public ImageBarnnerListener getListener() {
           return listener;
      }

      public void setListener(ImageBarnnerListener listener) {
           this.listener = listener;
      }

      public interface ImageBarnnerListener {
           void clickImageIndex(int pos); //pos代表的是我们当前图片的具体索引值
      }

      private ImageBarnnerViewGroupListener barnnerViewGroupListener;

      public ImageBarnnerViewGroupListener getBarnnerViewGroupListener() {
          return barnnerViewGroupListener;
      }

     public void setBarnnerViewGroupListener(
          ImageBarnnerViewGroupListener barnnerViewGroupListener) {
          this.barnnerViewGroupListener = barnnerViewGroupListener;
     }

     /**
      * 要想实现图片轮播底部圆点以及底部圆点切换功能步骤思路:
      * 1.我们需要自定义一个继承自FrameLayout的布局,利用FrameLayout布局的特性(在同一位置放置不同的view最终显示的是最后一个View)
      * 我们就可以实现底部圆点的布局。
      * 2.我们需要准素材,就是底部素材,我们可以利用Drawable的功能 去实现一个圆点图片的展示
      * 3.我们就需要继承FrameLayout 来自定义一个类,在该类的实现过程中,我们去加载我们刚才自定义的ImageBarnnerViewGroup核心类和我们
      * 需要的布局LinearLayout来实现。
      */
     //===自动轮播
     /**
      * 我会采用Timer,TimerTask,Handler三者结合的方式来实现自动轮播
      * 我们会抽出2个方法来控制,是否启动自动轮播,我们称之为startAuto(),stopAuto();
      * 那么我们在2个方法控制过程中,我们实际上希望控制的是自动开启轮播图的开关
      * 那么我们就需要一个变量参数 来作为我们自动开启轮播图的开关,我们称之为isAuto boolean true代表开启 false代表关闭
      * @param context
      */
     private boolean isAuto = true; //默认情况下我们是开启自动轮播
     private Timer timer = new Timer();
     private TimerTask task;
     private Handler autoHandler = new Handler() {
         public void handleMessage(android.os.Message msg) {
             switch(msg.what) {
             case 0: //此时我们需要图片的自动轮播
                 if(++index >= children) { //说明我们 此时如果是最后一张图片的话,那么我们将会从第一章图片开始重新滑动
                     index = 0;
                 }
            
                 scrollTo(childwidth * index, 0);
                 barnnerViewGroupListener.selectImage(index);
                 break;
             }
        
         };
     };

     private void startAuto() {
          isAuto = true;
     }

     private void stopAuto() {
         isAuto = false;
     }
     public ImageBarnnerViewGroup(Context context) {
         super(context);
         initobj();
     }

     public ImageBarnnerViewGroup(Context context, AttributeSet attrs) {
         super(context, attrs);
         initobj();
     }

     public ImageBarnnerViewGroup(Context context, AttributeSet attrs,
             int defStyle) {
         super(context, attrs, defStyle);
         initobj();
     }

     private void initobj() {
         scroller = new Scroller(getContext());
    
         task = new TimerTask() {
             public void run() {
                 if(isAuto) { //开启轮播图
                autoHandler.sendEmptyMessage(0);
                 }
             }
         };
    
         timer.schedule(task, 100,1000);
     }

     @Override
     public void computeScroll() {
         super.computeScroll();
         if(scroller.computeScrollOffset()) { //当前scroller对象是否已经滑动完毕
             scrollTo(scroller.getCurrX(), 0);
             invalidate();
         }
     }

     /**
      * 我们在自定义ViewGroup中,我们必须要实现的方法有:测量-》布局 -》绘制
      * 那么对于测量来说就是:onMeasure()
      * 我们对于绘制来说,因为我们是自定义的ViewGroup容器 针对于容器的绘制
      * 其实就是容器内的子控件的绘制过程,那么我们只需要 调用系统自带的绘制即可 也就说,对于viewGroup绘制过程不需要再重写该方法
      * 调用系统自带即可。
      */
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         /**
          * 由于我们要实现的是一个viewGroup的容器,那么
          * 我们就应该需要知道该容器中的子视图
          * 我们要想测量我们的ViewGroup的宽度和高度,那么我们就必须先要测量子视图
          * 的宽度和高度之和,才能知道我们的ViewGroup的宽度和高度是多少
          */
         //1.求出子视图的个数
          children = getChildCount(); //我们就可以知道子视图的个数
         if(0 == children) {
             setMeasuredDimension(0, 0);
         } else {
             //2.测量子视图的宽度和高度
             measureChildren(widthMeasureSpec, heightMeasureSpec);
             //此时我们一第一个子视图为基础,也就是说我们的ViewGroup的高度就是我们第一个子视图的高度
             //宽度就是我们第一个子视图的宽度*子视图的个数
             View view = getChildAt(0); //因为此时第一个视图绝对是存在的
        
             //3.根据子视图的宽度和高度,来求出该ViewGroup的宽度和高度
             childheight = view.getMeasuredHeight();
             childwidth =  view.getMeasuredWidth();
             int childwidth = view.getMeasuredWidth() * children; //宽度是我们所有字视图的宽度的总和
             setMeasuredDimension(childwidth, childheight);
         }
     }

     /**
           * 事件的传递过程中调用方法,我们需要调用容器的拦截方法 onInterceptTouchEvent
      * 针对于该方法我们可以理解为 如果说该方法的返回值为trued的时候,那么我们自定义的ViewGroup容器就会处理此次拦截事件
      * 如果说 返回值为false的时候,那么我们自定义的ViewGroup 容器将不会接受此次事件的处理过程,将会继续向下传递该事件, 
      * 针对于我们自定义的ViewGroup 我们当然是希望我们的ViewGroup 容器处理接受事件 那么我们的返回值就是true
      * 如果返回true的话  真正处理该事件的方法 是onTouch方法
      */
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
           return true;
    }
    /**
      * 用2中方式 来实现 轮播图的手动 轮播
      * 1.利用scrollTo scrollBy完成轮播图的手动轮播
      * 2.利用Scroller 对象 完成轮播图的手动轮播
      * 
      * 第一:我们在滑动屏幕图片的过程中,其实就是我们自定义ViewGroup 的子视图的移动过程,那么我们只需要知道
      * 滑动之前的横坐标和滑动之后的横坐标,此时 我们就可以此过程中移动的距离,我们在利用scrollBy方法实现图片的滑动
      * 所以 此时我们需要2个值 是需要我们 求出的: 移动之前,移动之后的 横坐标值
      * 
      * 第二:当我们第一次 按下得那一瞬间, 此时的移动之前和移动之后的值是相等的,也就是我们此时按下那一瞬间得哪一个点的横坐标的值。
      * 
      * 第三:我们在不断的滑动过程中,是会不断地调用我们ACTION_MOVE方法,那么此时我们就应该将 移动之前的值 和移动之后的进行保存。
      * 此时我们能够算出滑动的距离
      * 
      * 第四: 在我们抬起那一瞬间 我们需要计算出我们此时将要滑动到那张图片的位置上。
      * 
      * 我们此时就需要求得出将要滑动到的那张图片的索引值,
      * (我们当前ViewGroup的滑动位置 +我们的每一张图片的宽度/2)/我们的每一张图片的宽度值
      * 
      * 此时我们就可以利用scrollTo方法滑动到该图片的位置上。
      */
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         switch(event.getAction()) {
         case MotionEvent.ACTION_DOWN:
             stopAuto();
             if(!scroller.isFinished()) {
                 scroller.abortAnimation();
             }
             isClick = true;
             x = (int) event.getX();
             break;
         case MotionEvent.ACTION_MOVE:
             int moveX = (int) event.getX();
             int distance = moveX - x;
             scrollBy(-distance, 0);
             x = moveX;
             isClick = false;
             break;
         case MotionEvent.ACTION_UP:
             int scrollX = getScrollX(); //当前ViewGroup的滑动位置 
             index = (scrollX + childwidth/2)/childwidth;
        
             if(index < 0) { //说明了此事已经滑动到了最左边第一张图片
                 index = 0;
             } else if (index > children -1) { //说明了此事已经滑动到了最右最后一张图片
                 index = children -1;
             }
        
             if(isClick) {
                 listener.clickImageIndex(index);
             } else {
                 int dx = index * childwidth - scrollX; //当前滑动的距离
                 //scrollTo(index*childwidth, 0);
                 scroller.startScroll(scrollX, 0, dx, 0);
                 postInvalidate(); 
                 barnnerViewGroupListener.selectImage(index);
             }
             startAuto();
        
             break;
         default:
             break;
         }
         return true; //返回true 的目的是告诉
     }

     /**
      * 继承ViewGroup必须要实现布局onLayout方法
      * @param change 代表的时候是当我们的ViewGroup布局位置发生改变的为true 没有发生改变为false
      */
     @Override
     protected void onLayout(boolean change, int l, int t, int r, int b) {
         if(change) {
             int leftMargin = 0;
             for(int i = 0; i < children; i++) {
                 View view = getChildAt(i); //拿出每个视图
                 view.layout(leftMargin, 0, leftMargin + childwidth, childheight);
                 leftMargin +=childwidth;
             }
         }
     }

     public interface ImageBarnnerViewGroupListener {
         void selectImage(int index);
     }

    }
4.自定义l轮播图底部圆点布局实现

4.1实现图片轮播底部圆点以及底部圆点切换功能步骤思路:

  • 1.需要自定义一个继承自FrameLayout的布局,利用FrameLayout布局的特性(在同一位置放置不同的view最终显示的是最后一个View) 就可以实现底部圆点的布局。

  • 2.需要准素材,就是底部素材,我们可以利用Drawable的功能 去实现一个圆点图片的展示

  • 3.就需要继承FrameLayout 来自定义一个类,在该类的实现过程中,我们去加载我们刚才自定义的ImageBarnnerViewGroup核心类和我们 需要的布局LinearLayout来实现。
    public class ImageBarnnerFramLayout extends FrameLayout implements ImageBarnnerViewGroupListener,ImageBarnnerListener {
    private ImageBarnnerViewGroup mImageBarnnerViewGroup;
    private LinearLayout linearLayout;
    private Context mContext;
    public ImageBarnnerFramLayout(Context context) {
    super(context);
    mContext =context;
    initImageBarnnerViewGroup();
    initDoLinearLayout();
    }

    public ImageBarnnerFramLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext =context;
        initImageBarnnerViewGroup();
        initDoLinearLayout();
    }
    
    public ImageBarnnerFramLayout(Context context, AttributeSet attrs,
          int defStyle) {
         super(context, attrs, defStyle);
         mContext =context;
         initImageBarnnerViewGroup();
         initDoLinearLayout();
    }
    
    /**
     * 初始化 我们的底部圆点布局
     */
    private void initDoLinearLayout() {
       linearLayout = new LinearLayout(getContext());
       FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 40);
       linearLayout.setLayoutParams(lp);
       linearLayout.setOrientation(LinearLayout.HORIZONTAL);
       linearLayout.setGravity(Gravity.CENTER);
       
       linearLayout.setBackgroundColor(Color.RED);
       
       addView(linearLayout);
        
       FrameLayout.LayoutParams layoutParams = (LayoutParams) linearLayout.getLayoutParams();
       layoutParams.gravity = Gravity.BOTTOM;
       
       linearLayout.setLayoutParams(layoutParams);
       
       //这里有一个知识点,就是3.0以后 我们使用的是setAlpha() 在3.0之前我们使用的是setAlpha(),但是调用者不同
       if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
           linearLayout.setAlpha(0.5f);
       } else {
           linearLayout.getBackground().setAlpha(100);
       }
    }
    

    /**
    * 初始化 我们的自定义的图片轮播功能核心类
    */
    private void initImageBarnnerViewGroup() {
    mImageBarnnerViewGroup = new ImageBarnnerViewGroup(getContext());
    FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,FrameLayout.LayoutParams.MATCH_PARENT);
    mImageBarnnerViewGroup.setLayoutParams(lp);
    mImageBarnnerViewGroup.setListener(this);
    mImageBarnnerViewGroup.setBarnnerViewGroupListener(this);
    addView(mImageBarnnerViewGroup);

    }
    
    public void addBitmaps(List<Bitmap> list) {
        for(int i =0; i<list.size(); i++) {
            Bitmap bitmap = list.get(i);
            addBitmapToImageBarnnerViewGroup(bitmap);
            addDotToLinearlayout();
      }
    }
    
    private void addDotToLinearlayout() {
        ImageView iv = new ImageView(getContext());
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        lp.setMargins(5, 5, 5, 5);
        iv.setLayoutParams(lp);
        iv.setImageResource(R.drawable.dot_normal);
        linearLayout.addView(iv);
    }
    
    private void addBitmapToImageBarnnerViewGroup(Bitmap bitmap) {
        ImageView iv = new ImageView(getContext());
        //iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
        //iv.setLayoutParams(new ViewGroup.LayoutParams(windth, ViewGroup.LayoutParams.WRAP_CONTENT));
        //iv.setImageResource(ids[i]);
        iv.setImageBitmap(bitmap);
        mImageBarnnerViewGroup.addView(iv);
      
    }
    
    @Override
    public void selectImage(int index) {
        int count = linearLayout.getChildCount();
        for(int i=0; i<count; i++) {
            ImageView iv = (ImageView) linearLayout.getChildAt(i);
            if(i == index) {
                iv.setImageResource(R.drawable.dot_select);
            } else {
                iv.setImageResource(R.drawable.dot_normal);
            }
        }
    }
    
    @Override
    public void clickImageIndex(int pos) {
        Toast.makeText(mContext, "pos == "+pos, Toast.LENGTH_SHORT).show();
     }
    }
    

具体Drawable没有选中底图效果:

  <?xml version="1.0" encoding="utf-8"?>
  <shape xmlns:android="http://schemas.android.com/apk/res/android" 
        android:shape="oval">
        <solid android:color="@android:color/white"/>
        <size android:height="10dp"
        android:width="10dp"/
  </shape>

具体Drawable选中底图效果:
<?xml version="1.0" encoding="utf-8"?>

  <shape xmlns:android="http://schemas.android.com/apk/res/android" 
        android:shape="oval">

     <solid android:color="@android:color/black"/>

     <size android:height="10dp"
            android:width="10dp"/>

  </shape>

效果图如下:

1.png

2.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容