Android自定义流式标签控件

最近总感觉写博客的激情不高,不知道为啥。放上效果图,demo在最下面

图上那个切换按钮的作用呢,就是模拟改变标签的个数动态变化整个控件的高度。

其实这个控件也算很简单的控件了。关键点只有两个

  • 如何控制标签自动换行
  • 切换数据源时动态改变控件的高度

再简单的控件也需要一点一点的码出来,咱就从最基础的属性设置开始。

    public FlowTagView textColor(int defaultColor, int selectedColor){
        this.textColorDefault = defaultColor;
        this.textColorSelected = selectedColor;
        return this;
    }

    public FlowTagView textSize(int textSize){
        this.textSize = textSize;
        return this;
    }

    public FlowTagView backgroundColor(int defaultColor, int selectedColor){
        this.backgroundColorDefault = defaultColor;
        this.backgroundColorSelected = selectedColor;
        return this;
    }

    public FlowTagView padding(int horizontalPadding, int verticalPadding, int textHorizontalPadding){
        this.horizontalPadding = horizontalPadding;
        this.verticalPadding = verticalPadding;
        this.textHorizontalPadding = textHorizontalPadding;
        return this;
    }

    public FlowTagView itemHeight(int height){
        this.itemHeight = height;
        return this;
    }

    public FlowTagView datas(String[] datas){
        this.datas = datas;
        return this;
    }

    public FlowTagView listener(OnTagSelectedListener listener){
        this.listener = listener;
        return this;
    }

上面设置了字体颜色啊,背景颜色啊,标签Item的高度啊,内补白和外部白的一些值,还有一个监听器。有的朋友就说了,我比较懒,就想快点看到效果,不想设置怎么办?怎么办?给默认值呗。

    //常亮默认值,这些参数若不调用方法传递,则直接使用默认值
    public static final int ROUND_RADIUS = 30;
    public static final int TEXT_COLOR_DEFAULT = Color.BLACK;
    public static final int TEXT_COLOR_SELECTED = Color.WHITE;
    public static final int TEXT_SIZE = 30;
    public static final int BACKGROUND_COLOR_DEFAULT = Color.GRAY;
    public static final int BACKGROUND_COLOR_SELECTED = Color.GREEN;
    public static final int HORIZONTAL_PADDING = 30;
    public static final int VERTICAL_PADDING = 30;
    public static final int TEXT_HORIZONTAL_PADDING = 30;
    public static final int ITEM_HEIGHT = 60;

    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private int textColorDefault = TEXT_COLOR_DEFAULT;
    private int textColorSelected = TEXT_COLOR_SELECTED;
    private int textSize = TEXT_SIZE;
    private int backgroundColorDefault = BACKGROUND_COLOR_DEFAULT;
    private int backgroundColorSelected = BACKGROUND_COLOR_SELECTED;
    //Tag之间的横向和纵向的间隔
    private int horizontalPadding = HORIZONTAL_PADDING;
    private int verticalPadding = VERTICAL_PADDING;
    //每个Tag内部的横向间隔
    private int textHorizontalPadding = TEXT_HORIZONTAL_PADDING;
    //每个Tag的高度
    private int itemHeight = ITEM_HEIGHT;

好了,基本的属性设置的代码完成了,那么就用软件的高内聚低耦合的思想封装一个标签类吧。

    public class Tag{
        //文本属性
        public String text;
        public int textColorDefault;
        public int textColorSelected;
        public int backgroundColorDefault;
        public int backgroundColorSelected;
        public boolean isSelected;
        public Paint paint;
        //文本的绘制起点
        public int drawX;
        public int drawY;
        //整个Tag占用的坐标范围
        public RectF rect = new RectF();

        public Tag(String text, int textSize, int textColorDefault, int textColorSelected, 
                   int backgroundColorDefault, int backgroundColorSelected,
                   Paint paint, int height, int horizontalPadding, int startX, int startY){
            this.text = text;
            this.textColorDefault = textColorDefault;
            this.textColorSelected = textColorSelected;
            this.backgroundColorDefault = backgroundColorDefault;
            this.backgroundColorSelected = backgroundColorSelected;
            this.paint = paint;
            //求出整个Tag的宽度
            paint.setTextSize(textSize);
            int textWidth = (int)paint.measureText(text);
            int width = textWidth + 2 * horizontalPadding;
            //计算坐标范围,startX,staryY是指左上角的起点
            rect.left = startX;
            rect.top = startY;
            rect.right = startX + width;
            rect.bottom = startY + height;
            //计算居中绘制时的绘制起点
            drawX = startX + horizontalPadding;
            Paint.FontMetrics metrics =  paint.getFontMetrics();
            drawY = (int)(startY + height / 2 + (metrics.bottom - metrics.top) / 2 - metrics.bottom);
        }

        public void draw(Canvas canvas){
            if(isSelected){
                //绘制背景
                paint.setColor(backgroundColorSelected);
                paint.setStyle(Paint.Style.FILL);
                canvas.drawRoundRect(rect, ROUND_RADIUS, ROUND_RADIUS, paint);
                //绘制文本
                paint.setColor(textColorSelected);
                canvas.drawText(text, drawX, drawY, paint);
            }else{
                //绘制背景
                paint.setColor(backgroundColorDefault);
                paint.setStyle(Paint.Style.STROKE);
                canvas.drawRoundRect(rect, ROUND_RADIUS, ROUND_RADIUS, paint);
                //绘制文本
                paint.setColor(textColorDefault);
                canvas.drawText(text, drawX, drawY, paint);
            }
        }

    }

这个封装类就两个方法,一个是构造方法,一个是绘制方法。构造方法就是对属性的一些赋值。然后利用startX和startY计算出每个标签的坐标范围和文本的绘制起点。绘制方法draw(Canvas canvas)就简单得绘制一个文本和一个背景。想定制标签的样式的话,就在这个方法进行重写。

好了,这个封装类其实也不算难。接下来就来到最关键的地方了。startX和startY的取值。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        int width = MeasureSpec.getSize(widthMeasureSpec);
        //算出绘制起点
        startX = getPaddingLeft();
        startY = getPaddingTop();
        tags.clear();
        for(int i = 0; i < datas.length; i++){
            //判断是否越过边界
            if(startX + getRealWidth(paint, textSize, datas[i], textHorizontalPadding) + horizontalPadding
                        > width - getPaddingRight()){
                //在下一行开始绘制
                startX = getPaddingLeft();
                startY += itemHeight + verticalPadding;
            }
            Tag tag = new Tag(datas[i], textSize, textColorDefault, textColorSelected,
                    backgroundColorDefault, backgroundColorSelected, paint, itemHeight, textHorizontalPadding, startX, startY);
            tags.add(tag);
            //动态更新值
            startX += getRealWidth(paint, textSize, datas[i], textHorizontalPadding) + horizontalPadding;
        }
        //算出整个控件需要的高度
        int height = startY + itemHeight + getPaddingBottom();
        setMeasuredDimension(width, height);
    }

这里用到了一个工具方法getRealWidth,这个就是用来计算每一个标签的真实宽度的。

    /**
     * 根据参数算出某个Tag所需要占用的宽度值,包括内补白
     */
    public static int getRealWidth(Paint paint, int textSize, String text, int textHorizontalPadding){
        paint.setTextSize(textSize);
        int textWidth = (int)paint.measureText(text);
        return textWidth + 2 * textHorizontalPadding;
    }

代码不多,但是的确是最重要的地方。首先拿到startX和startY的初始值。默认为padding值。然后对文本进行遍历。当当前文本的绘制终点大于该行的最大值,则重置startX,并且将startY累加一次标签的高度值与竖直补白值。然后进行该标签的实例化。然后别忘了对startX进行重新赋值。最后得到整个控件实际得高度,设置该控件的高度。

有的小伙伴就问了,要是我的数据源发生了变化,怎么动态改变高度值以及刷新数据源呢。这也是我刚才提到的第二个重点,这个问题我找了很多办法,最优秀的办法就是利用LayoutParams。

    public void commit(){
        if(datas == null){
            Log.e("FlowTagView", "maybe not invok the method named datas(String[])");
            throw new IllegalStateException("maybe not invok the method named datas(String[])");
        }
        paint.setTextSize(textSize);
        if(datas.length != tags.size()){
            //重新实例化
            ViewGroup.LayoutParams params = getLayoutParams();
            setLayoutParams(params);
        }
    }

在外界设置属性的时候,最后一个链一定要调用commit方法进行提交,这里直接得到当前的LayoutParams,然后再次设置回去。这样做有什么用呢?用处就是为了触发onMeasure方法。哈哈,onMeasure方法会自动进行重计算的。机智如我。

接下来就处理点击事件了,首先定义一个自定义的接口。

    public interface OnTagSelectedListener{
        void onTagSelected(FlowTagView view, int position);
    }

祭出最最最常用的onTouchEvent方法,前提是有几个成员变量。

    //点击事件的滑动距离阈值
    private int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    //ACTION_DOWN时的坐标值
    private float mTouchX;
    private float mTouchY;
    //ACTION_DOWN时选中的tag的索引
    private int mTouchPosition;

onTouchEvent方法进行事件分发。

    @Override
    public boolean onTouchEvent(MotionEvent event){
        switch(event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mTouchX = event.getX();
                mTouchY = event.getY();
                mTouchPosition = getTagPosition(mTouchX, mTouchY);
                return true;

            case MotionEvent.ACTION_UP:
                float mUpX = event.getX();
                float mUpY = event.getY();
                //滑动距离小于点击阈值并且点击时的索引值不是非法值,并且up时的索引值和down时的索引值相等时,才触发选中操作
                if(Math.abs(mUpX - mTouchX) < mTouchSlop && Math.abs(mUpY - mTouchY) < mTouchSlop
                        && mTouchPosition != -1 && getTagPosition(mUpX, mUpY) == mTouchPosition){
                    //触发点击选中
                    setSelect(mTouchPosition);
                }
                break;
        }
        return super.onTouchEvent(event);
    }

其实就是一个模拟点击的操作。对于抬起和按下时的坐标不超过一个给定阈值,并且抬起和按下时点击的标签是同一个的话,才触发选中的操作。也就是setSelect方法。

    /**
     * 根本坐标值,返回对应的tag的索引,若不存在则返回-1
     */
    private int getTagPosition(float x, float y){
        for(int i = 0; i < tags.size(); i++){
            if(tags.get(i).rect.contains(x, y)){
                return i;
            }
        }
        return -1;
    }

    public void setSelect(int position){
        if(position < 0 || position >= tags.size()){
            Log.e("FlowTagView", "the position is illetal");
            throw new IllegalArgumentException("the position is illetal");
        }
        for(int i = 0; i < tags.size(); i++){
            //关闭其他选择
            if(i != position){
                tags.get(i).isSelected = false;
            }else{
                tags.get(i).isSelected = true;
            }
        }
        //触发监听器
        if(listener != null){
            listener.onTagSelected(this, position);
        }
        //必须要刷新UI
        invalidate();
    }

    public int getSelect(){
        for(int i = 0; i < tags.size(); i++){
            if(tags.get(i).isSelected){
                return i;
            }
        }
        return -1;
    }

好了,这个自定义控件的讲解就结束了,按照我的习惯,此时应该贴出这个控件的完整代码,我相信不少小伙伴儿会因为字多而忽略掉。。

package cc.wxf.component;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by ccwxf on 2016/7/21.
 */
public class FlowTagView extends View {
    //常亮默认值,这些参数若不调用方法传递,则直接使用默认值
    public static final int ROUND_RADIUS = 30;
    public static final int TEXT_COLOR_DEFAULT = Color.BLACK;
    public static final int TEXT_COLOR_SELECTED = Color.WHITE;
    public static final int TEXT_SIZE = 30;
    public static final int BACKGROUND_COLOR_DEFAULT = Color.GRAY;
    public static final int BACKGROUND_COLOR_SELECTED = Color.GREEN;
    public static final int HORIZONTAL_PADDING = 30;
    public static final int VERTICAL_PADDING = 30;
    public static final int TEXT_HORIZONTAL_PADDING = 30;
    public static final int ITEM_HEIGHT = 60;

    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private int textColorDefault = TEXT_COLOR_DEFAULT;
    private int textColorSelected = TEXT_COLOR_SELECTED;
    private int textSize = TEXT_SIZE;
    private int backgroundColorDefault = BACKGROUND_COLOR_DEFAULT;
    private int backgroundColorSelected = BACKGROUND_COLOR_SELECTED;
    //Tag之间的横向和纵向的间隔
    private int horizontalPadding = HORIZONTAL_PADDING;
    private int verticalPadding = VERTICAL_PADDING;
    //每个Tag内部的横向间隔
    private int textHorizontalPadding = TEXT_HORIZONTAL_PADDING;
    //每个Tag的高度
    private int itemHeight = ITEM_HEIGHT;

    //tag的绘制起点,动态计算得值
    private int startX;
    private int startY;
    //Tag显示的文本
    private String[] datas;
    private List<Tag> tags = new ArrayList<Tag>();

    //点击事件的滑动距离阈值
    private int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    //ACTION_DOWN时的坐标值
    private float mTouchX;
    private float mTouchY;
    //ACTION_DOWN时选中的tag的索引
    private int mTouchPosition;

    private OnTagSelectedListener listener;

    public FlowTagView(Context context, AttributeSet attrs, int defStyleAttr){
        super(context, attrs, defStyleAttr);
    }

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

    public FlowTagView(Context context){
        super(context);
    }

    public FlowTagView textColor(int defaultColor, int selectedColor){
        this.textColorDefault = defaultColor;
        this.textColorSelected = selectedColor;
        return this;
    }

    public FlowTagView textSize(int textSize){
        this.textSize = textSize;
        return this;
    }

    public FlowTagView backgroundColor(int defaultColor, int selectedColor){
        this.backgroundColorDefault = defaultColor;
        this.backgroundColorSelected = selectedColor;
        return this;
    }

    public FlowTagView padding(int horizontalPadding, int verticalPadding, int textHorizontalPadding){
        this.horizontalPadding = horizontalPadding;
        this.verticalPadding = verticalPadding;
        this.textHorizontalPadding = textHorizontalPadding;
        return this;
    }

    public FlowTagView itemHeight(int height){
        this.itemHeight = height;
        return this;
    }

    public FlowTagView datas(String[] datas){
        this.datas = datas;
        return this;
    }

    public FlowTagView listener(OnTagSelectedListener listener){
        this.listener = listener;
        return this;
    }

    public void commit(){
        if(datas == null){
            Log.e("FlowTagView", "maybe not invok the method named datas(String[])");
            throw new IllegalStateException("maybe not invok the method named datas(String[])");
        }
        paint.setTextSize(textSize);
        if(datas.length != tags.size()){
            //重新实例化
            ViewGroup.LayoutParams params = getLayoutParams();
            setLayoutParams(params);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        int width = MeasureSpec.getSize(widthMeasureSpec);
        //算出绘制起点
        startX = getPaddingLeft();
        startY = getPaddingTop();
        tags.clear();
        for(int i = 0; i < datas.length; i++){
            //判断是否越过边界
            if(startX + getRealWidth(paint, textSize, datas[i], textHorizontalPadding) + horizontalPadding > width - getPaddingRight()){
                //在下一行开始绘制
                startX = getPaddingLeft();
                startY += itemHeight + verticalPadding;
            }
            Tag tag = new Tag(datas[i], textSize, textColorDefault, textColorSelected,
                    backgroundColorDefault, backgroundColorSelected, paint, itemHeight, textHorizontalPadding, startX, startY);
            tags.add(tag);
            //动态更新值
            startX += getRealWidth(paint, textSize, datas[i], textHorizontalPadding) + horizontalPadding;
        }
        //算出整个控件需要的高度
        int height = startY + itemHeight + getPaddingBottom();
        setMeasuredDimension(width, height);
    }

    /**
     * 根据参数算出某个Tag所需要占用的宽度值,包括内补白
     */
    public static int getRealWidth(Paint paint, int textSize, String text, int textHorizontalPadding){
        paint.setTextSize(textSize);
        int textWidth = (int)paint.measureText(text);
        return textWidth + 2 * textHorizontalPadding;
    }

    @Override
    protected void onDraw(Canvas canvas){
        //绘制代理
        for(int i = 0; i < tags.size(); i++){
            tags.get(i).draw(canvas);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        switch(event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mTouchX = event.getX();
                mTouchY = event.getY();
                mTouchPosition = getTagPosition(mTouchX, mTouchY);
                return true;

            case MotionEvent.ACTION_UP:
                float mUpX = event.getX();
                float mUpY = event.getY();
                //滑动距离小于点击阈值并且点击时的索引值不是非法值,并且up时的索引值和down时的索引值相等时,才触发选中操作
                if(Math.abs(mUpX - mTouchX) < mTouchSlop && Math.abs(mUpY - mTouchY) < mTouchSlop
                        && mTouchPosition != -1 && getTagPosition(mUpX, mUpY) == mTouchPosition){
                    //触发点击选中
                    setSelect(mTouchPosition);
                }
                break;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 根本坐标值,返回对应的tag的索引,若不存在则返回-1
     */
    private int getTagPosition(float x, float y){
        for(int i = 0; i < tags.size(); i++){
            if(tags.get(i).rect.contains(x, y)){
                return i;
            }
        }
        return -1;
    }

    public void setSelect(int position){
        if(position < 0 || position >= tags.size()){
            Log.e("FlowTagView", "the position is illetal");
            throw new IllegalArgumentException("the position is illetal");
        }
        for(int i = 0; i < tags.size(); i++){
            //关闭其他选择
            if(i != position){
                tags.get(i).isSelected = false;
            }else{
                tags.get(i).isSelected = true;
            }
        }
        //触发监听器
        if(listener != null){
            listener.onTagSelected(this, position);
        }
        //必须要刷新UI
        invalidate();
    }

    public int getSelect(){
        for(int i = 0; i < tags.size(); i++){
            if(tags.get(i).isSelected){
                return i;
            }
        }
        return -1;
    }

    public class Tag{
        //文本属性
        public String text;
        public int textColorDefault;
        public int textColorSelected;
        public int backgroundColorDefault;
        public int backgroundColorSelected;
        public boolean isSelected;
        public Paint paint;
        //文本的绘制起点
        public int drawX;
        public int drawY;
        //整个Tag占用的坐标范围
        public RectF rect = new RectF();

        public Tag(String text, int textSize, int textColorDefault, int textColorSelected, int backgroundColorDefault, int backgroundColorSelected,
                   Paint paint, int height, int horizontalPadding, int startX, int startY){
            this.text = text;
            this.textColorDefault = textColorDefault;
            this.textColorSelected = textColorSelected;
            this.backgroundColorDefault = backgroundColorDefault;
            this.backgroundColorSelected = backgroundColorSelected;
            this.paint = paint;
            //求出整个Tag的宽度
            paint.setTextSize(textSize);
            int textWidth = (int)paint.measureText(text);
            int width = textWidth + 2 * horizontalPadding;
            //计算坐标范围,startX,staryY是指左上角的起点
            rect.left = startX;
            rect.top = startY;
            rect.right = startX + width;
            rect.bottom = startY + height;
            //计算居中绘制时的绘制起点
            drawX = startX + horizontalPadding;
            Paint.FontMetrics metrics =  paint.getFontMetrics();
            drawY = (int)(startY + height / 2 + (metrics.bottom - metrics.top) / 2 - metrics.bottom);
        }

        public void draw(Canvas canvas){
            if(isSelected){
                //绘制背景
                paint.setColor(backgroundColorSelected);
                paint.setStyle(Paint.Style.FILL);
                canvas.drawRoundRect(rect, ROUND_RADIUS, ROUND_RADIUS, paint);
                //绘制文本
                paint.setColor(textColorSelected);
                canvas.drawText(text, drawX, drawY, paint);
            }else{
                //绘制背景
                paint.setColor(backgroundColorDefault);
                paint.setStyle(Paint.Style.STROKE);
                canvas.drawRoundRect(rect, ROUND_RADIUS, ROUND_RADIUS, paint);
                //绘制文本
                paint.setColor(textColorDefault);
                canvas.drawText(text, drawX, drawY, paint);
            }
        }

    }

    public interface OnTagSelectedListener{
        void onTagSelected(FlowTagView view, int position);
    }
}

最后一段代码一定是放使用方法,这是我的习惯。。

<?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"
    >
<Button
    android:id="@+id/btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="切换"
    />

    <cc.wxf.component.FlowTagView
        android:id="@+id/tagView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        />
</LinearLayout>
package cc.wxf.androiddemo;

import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import cc.wxf.component.FlowTagView;

public class MainActivity extends Activity {

    private int i = 0;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final String[] datas1 = new String[]{
                "推荐", "电影", "电视剧", "头条", "娱乐", "动漫", "猜你喜欢", "资讯", "搞笑", "体育", "综艺", "片花", "少儿", "今日头条", "娱乐", "动漫", "猜你喜欢", "资讯", "搞笑", "体育", "综艺"
        };
        final String[] datas2 = new String[]{
                "推荐", "电影", "电视剧", "头条", "娱乐", "动漫", "猜你喜欢", "资讯"
        };
        Resources resources = getResources();
        final FlowTagView tagView = (FlowTagView) findViewById(R.id.tagView);
        tagView.datas(datas1)
                //下面的5个方法若不设置,则会采用默认值
                .textColor(resources.getColor(android.R.color.darker_gray), resources.getColor(android.R.color.white))
                .textSize(sp2px(15))
                .backgroundColor(resources.getColor(android.R.color.darker_gray), resources.getColor(android.R.color.holo_green_light))
                .itemHeight(dp2px(40))
                .padding(dp2px(10), dp2px(10), dp2px(15))
                //上面的5个方法若不设置,则会采用默认值
                .listener(new FlowTagView.OnTagSelectedListener() {
                    @Override
                    public void onTagSelected(FlowTagView view, int position) {
                        Toast.makeText(MainActivity.this, "选中了:" + position, Toast.LENGTH_SHORT).show();
                    }
                })
                //commit必须调用
                .commit();
        //模拟标签的个数发生变化,造成控件的自动伸展
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                i ++;
                //commit必须调用
                tagView.datas(i % 2 == 0 ? datas1 : datas2).commit();
            }
        });
    }

    public int sp2px(int sp){
        float density = getResources().getDisplayMetrics().scaledDensity;
        return (int) (sp * density + 0.5f);
    }

    public int dp2px(int dp){
        float density = getResources().getDisplayMetrics().density;
        return (int) (dp * density + 0.5f);
    }
}

Over,最后是demo的下载地址。哎,最近写博客没激情,闭关一段时间算了。

点我去下载demo

时间: 2024-10-17 22:51:55

Android自定义流式标签控件的相关文章

android - 自定义(组合)控件 + 自定义控件外观

转载:http://www.cnblogs.com/bill-joy/archive/2012/04/26/2471831.html android - 自定义(组合)控件 + 自定义控件外观 Android自定义View实现很简单 继承View,重写构造函数.onDraw,(onMeasure)等函数. 如果自定义的View需要有自定义的属性,需要在values下建立attrs.xml.在其中定义你的属性. 在使用到自定义View的xml布局文件中需要加入xmlns:前缀="http://sc

Android自定义View之组合控件 ---- LED数字时钟

先上图 LEDView效果如图所示. 之前看到一篇博客使用两个TextView实现了该效果,于是我想用自定义控件的方式实现一个LEDView,使用时即可直接使用该控件. 采用组合控件的方式,将两个TextView叠放在一起,再使用digital-7.ttf字体来显示数据,从而达到LED的效果.代码如下: LEDView.class package ione.zy.demo; import java.io.File; import java.util.Calendar; import java.u

Android自定义组件之日历控件-精美日历实现(内容、样式可扩展)

需求 我们知道,Android系统本身有自带的日历控件,网络上也有很多开源的日历控件资源,但是这些日历控件往往样式较单一,API较多,不易于在实际项目中扩展并实现出符合具体样式风格的,内容可定制的效果.本文通过自定义日历控件,实现了在内容和样式上可高度扩展的精美日历demo,有需要的Android应用开发人员可迅速移植并按需扩展实现. 在某个应用中,需要查询用户的历史考勤记录,根据实际考勤数据在日历中标记出不同的状态(如正常出勤.请假.迟到等),并在页面中显示相应的说明文字. 效果 实现的效果如

[Android]自定义简易版日历控件

先来看看效果图,看看是不是各位大佬想要的: 特别的功能并不多,重点是讲解简易日历该如何构造,假若是项目着急要用的话,最好还是找一下其它人写好的日历(附加滑动改变日历日期等功能) ---------------------------------------------------------------------------------------华丽的分割线--------------------------------------------------------------------

Android自定义实现循环滚轮控件WheelView

首先呈上效果图 现在很多地方都用到了滚轮布局WheelView,比如在选择生日的时候,风格类似系统提供的DatePickerDialog,开源的控件也有很多,不过大部分都是根据当前项目的需求绘制的界面,因此我就自己写了一款比较符合自己项目的WheelView. 首先这个控件有以下的需求: 1.能够循环滚动,当向上或者向下滑动到临界值的时候,则循环开始滚动 2.中间的一块有一块半透明的选择区,滑动结束时,哪一块在这个选择区,就选择这快. 3.继承自View进行绘制 然后进行一些关键点的讲解: 1.

Android自定义设置圆形图片控件

注:这篇文章是转载alan_biao博主的一篇文章,正好用到,觉得里面代码很精髓,贴出来并给与链接供需要的童鞋下载使用!已贴出核心代码和提供源码地址. Android自定义圆形图片,可设置最多两个的外边框,包括从网络获取图片显示. 1.解决图片锯齿问题. 2.解决图片变形问题. 效果图: 原始图片: 原文地址和源码下载链接:http://blog.csdn.net/alan_biao/article/details/17379925

Android 标签控件

版本:1.0 日期:2014.7.24 版权:© 2014 kince 转载注明出处 在有的应用中可能需要设置一些标签来方便用去去查询某些信息,比如手机助手或者购物软件之类都会有一些标签.对于软件开发初期来说,直接使用TextView.Button实现是最为简单的一种方式.但是这种方法也有其局限性,比如不能控制换行.耦合性低等缺点.所以除了解决这些问题之外,最好能够封装一个类库出来,方便以后使用. 首先新建一个Tag类, import java.io.Serializable; public c

Android注解式绑定控件,没你想象的那么难

Android开发中,有一个让人又爱又恨的方法叫findViewById(int);我想如果你是一民Android开发者,必然知道这个方法.那么为什么让人又爱又恨呢?想必大家也是很有感触.写一个布局,用Java代码写和用xml文件写,完成速度完全是无法比拟的.xml布局太方便了.同样的,想获取一个控件的对象,如果你是使用的xml布局文件写的布局,那么你必须调用findViewById()这个方法. TextView t = (TextView) findViewById(R.id.x); 这是我

自定义快速查找字母控件

效果图如下: 首先看看布局文件,自定义的控件中包含一个 ListView,用于显示具体的数据内容: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="fill_parent"     a