android自定义view,实现竖直方向的文字功能,文字方向朝上,同时提供接口,判断当前touch的是哪个字符,并改变颜色。

由于时间比较仓促,因此没有对代码进行过多的优化,功能远远不如android的自带的TextView强大,只是继承于view,而不是textview。

主要用途:电话本的侧边快速导航等

效果图:(自定义字符串 “#ABCDEFGHIJKLMN),可以实现自定义任意字符串

view的实现:

 package cn.carbs.verticalstraighttextview.view;

 import cn.carbs.verticalstraighttextview.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint.Align;
import android.graphics.Rect;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
/**
* 参考资料:
* http://chris.banes.me/2014/03/27/measuring-text/
* http://blog.163.com/gobby_1110/blog/static/2928171520136304172378/
* @author Rick.Wang
*
*/
public class VerticalStraightTextView extends View { private final static int DEFAULT_TEXT_SIZE = 15;
private final static int DEFAULT_TEXT_COLOR = 0xFF000000;
private final static int DEFAULT_TEXT_COLOR_PICKED = 0xFF990000;
private final static int DEFAULT_CHAR_SPACE = 0; private TextPaint mTextPaint;
private TextPaint mTextPaintPicked;
private String mText = ""; private int mTextLength = 0;
private int mCharGap = 0;
private int mCharWidth = 0;
private int mCharHeight = 0; private int currPickedCharIndex = -1; float[] coordinates = null; public float[] getCoordinates(){
return coordinates;
} public VerticalStraightTextView(Context context) {
super(context);
init();
} public VerticalStraightTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.verticalstraighttextview); int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.verticalstraighttextview_text:
mText = a.getString(attr);
if(mText == null){
mText = "";
break;
}
mTextLength = mText.length();
break;
case R.styleable.verticalstraighttextview_textSize:
int textSize = a.getDimensionPixelOffset(R.styleable.verticalstraighttextview_textSize, DEFAULT_TEXT_SIZE);
if (textSize > 0) {
mTextPaint.setTextSize(textSize);
mTextPaintPicked.setTextSize(textSize);
}
break; case R.styleable.verticalstraighttextview_charGap:
mCharGap = a.getDimensionPixelSize(R.styleable.verticalstraighttextview_charGap, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_PX, DEFAULT_CHAR_SPACE, getResources().getDisplayMetrics()));
break;
case R.styleable.verticalstraighttextview_textColor:
mTextPaint.setColor(a.getColor(R.styleable.verticalstraighttextview_textColor, DEFAULT_TEXT_COLOR));
break;
case R.styleable.verticalstraighttextview_textColorPicked:
mTextPaintPicked.setColor(a.getColor(R.styleable.verticalstraighttextview_textColorPicked, DEFAULT_TEXT_COLOR_PICKED));
break;
}
}
a.recycle(); requestLayout();
invalidate();
} private final void init() {
mTextPaint = new TextPaint();
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(DEFAULT_TEXT_SIZE);
mTextPaint.setTextAlign(Align.CENTER);
mTextPaintPicked = new TextPaint(mTextPaint);
mTextPaint.setColor(DEFAULT_TEXT_COLOR);
mTextPaintPicked.setColor(DEFAULT_TEXT_COLOR_PICKED);
} public void setText(String text) {
if(text == null){
text = "";
}
if(!mText.equals(text)){
mText = text;
mTextLength = text.length();
requestLayout();
invalidate();
}
} public void setTextSize(int size) {
if(mTextPaint.getTextSize() != size){
mTextPaint.setTextSize(size);
mTextPaintPicked.setTextSize(size);
requestLayout();
invalidate();
}
} public void setTextColor(int color) {
if(color != mTextPaint.getColor()){
mTextPaint.setColor(color);
invalidate();
}
} public void setTextColorPicked(int color) {
if(color != mTextPaintPicked.getColor()){
mTextPaintPicked.setColor(color);
invalidate();
}
} public int getCharHeight(){
return mCharGap + mCharHeight;
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d("1218", "onMeasure");
//获取字体宽度
float maxCharWidth = 0f;
for(int i = 0; i < mTextLength; i++){
maxCharWidth = Math.max(mTextPaint.measureText(mText.substring(i, i+1)), maxCharWidth);
}
mCharWidth = (int)Math.ceil(maxCharWidth); //获取字体高度
Rect textBounds = new Rect();
mTextPaint.getTextBounds(mText, 0, mTextLength, textBounds);
mCharHeight = textBounds.height(); setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
} private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = this.getPaddingLeft() + this.getPaddingRight() + mCharWidth;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
} private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = getPaddingTop() + getPaddingBottom();
if(mTextLength > 0){
result += mTextLength * (mCharGap + mCharHeight) - mCharGap;
}
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d("1218", "onDraw");
if(mTextLength == 0){
return;
} int height = getMeasuredHeight();
int measuredWidth = getMeasuredWidth(); int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight(); //默认居中
int x = paddingLeft + (measuredWidth - paddingLeft - paddingRight)/2;
int y = 0; int cellHeight = (height - paddingTop - paddingBottom)/ mTextLength;
//TODO 可能会有bug
if(coordinates == null || coordinates.length != mTextLength){
coordinates = new float[mTextLength + 1];
}
coordinates[0] = 0;
for(int i = 0; i < mTextLength; i++){
y = paddingTop + i * cellHeight + cellHeight/2;
coordinates[i + 1] = y + cellHeight/2;
if(currPickedCharIndex != i){
canvas.drawText(mText, i, i + 1, x, y, mTextPaint);
}else{
canvas.drawText(mText, i, i + 1, x, y, mTextPaintPicked);
}
}
coordinates[mTextLength] = height;
} //y is the coordinate-Y
//this function can return the "touched char"
public int getPickedCharIndex(float[] coordinates, float y){
int start = 0;
int end = coordinates.length - 1;
while (start != end - 1) {
int middle = (start + end) / 2;
if (y < coordinates[middle]) {
end = middle;
} else if (y > coordinates[middle]) {
start = middle;
}
}
return start;
} /***************************************
*
* +---------------+ <-- Y == coordinates[0]
* | # |
* |---------------| coordinates[1]
* | A |
* |---------------| coordinates[2]
* | B |
* |---------------| coordinates[3]
* | C |
* +---------------| coordinates[4]
***************************************/ public int getPickedCharIndex(float y){
//优化查询
//如果当前的>-1,说明正在touchEvent
if(currPickedCharIndex > -1){
if(coordinates[currPickedCharIndex] < y && y < coordinates[currPickedCharIndex+1]){
return currPickedCharIndex;
}
} int start = 0;
int end = coordinates.length - 1;
while (start != end - 1) {
int middle = (start + end) / 2;
if (y < coordinates[middle]) {
end = middle;
} else if (y > coordinates[middle]) {
start = middle;
}
}
return start;
} public void setCurrPickedCharIndex(int index){
if(currPickedCharIndex != index){
currPickedCharIndex = index;
invalidate();
}
} }

style文件的定义:(将此代码写入values文件夹下的styles.xml文件中)

 <declare-styleable name="verticalstraighttextview">
<attr name= "text" format ="string" />
<attr name= "textColor" format ="reference|color" />
<attr name= "textColorPicked" format="reference|color" />
<attr name= "textSize" format="reference|dimension" />
<attr name= "charGap" format ="reference|dimension" />
</declare-styleable >

布局文件引入此自定义view:

 <cn.carbs.verticalstraighttextview.view.VerticalStraightTextView
android:id="@+id/kk"
android:layout_width="wrap_content"
android:padding="5dp"
android:layout_height="fill_parent"
android:background="#33333333"
app:textSize="20sp"
app:text= "#ABCEDFGHIJKLMN" />

在activity中的使用:

 verticalView = (VerticalStraightTextView)this.findViewById(R.id.kk);

         verticalView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "onclick", Toast.LENGTH_SHORT).show();
}
}); verticalView.setOnTouchListener(new View.OnTouchListener() { @Override
public boolean onTouch(View view, MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
verticalView.setCurrPickedCharIndex(verticalView.getPickedCharIndex(event.getY()));
break;
case MotionEvent.ACTION_MOVE:
verticalView.setCurrPickedCharIndex(verticalView.getPickedCharIndex(event.getY()));
break;
case MotionEvent.ACTION_UP:
verticalView.setCurrPickedCharIndex(-1);
break;
case MotionEvent.ACTION_CANCEL:
verticalView.setCurrPickedCharIndex(-1);
break;
}
return true;
}
});

android中自定义view---实现竖直方向的文字功能,文字方向朝上,同时提供接口,判断当前touch的是哪个字符,并改变颜色的更多相关文章

  1. android开发:Android 中自定义View的应用

    大家好我们今天的教程是在Android 教程中自定义View 的学习,对于初学着来说,他们习惯了Android 传统的页面布局方式,如下代码: <?xml version="1.0&q ...

  2. android中自定义view构造函数ContentItemView(Context context, AttributeSet paramAttributeSet)的用处

    自己定义一个view <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:a ...

  3. Android中自定义View和自定义动画

    Android FrameWork 层给我们提供了很多界面组件,但是在实际的商业开发中这些组件往往并不能完全满足我们的需求,这时候我们就需要自定义我们自己的视图和动画. 我们要重写系统的View就必须 ...

  4. Android中自定义View的MeasureSpec使用

    有时,Android系统控件无法满足我们的需求,因此有必要自定义View.具体方法参见官方开发文档:http://developer.android.com/guide/topics/ui/custo ...

  5. Android 中的View与ViewGroup

    Android重点知识--View和ViewGroup与自定义控件 作者:丁明祥 邮箱:2780087178@qq.com 一.基础 ViewGroup 参考资料: Android 手把手教您自定义V ...

  6. 详解实现Android中实现View滑动的几种方式

    注: 本文提到的所有三种滑动方式的完整demo:ScrollDemo 1. 关于View我们需要知道的 (1)什么是View? Android中的View类是所有UI控件的基类(Base class) ...

  7. android中实现view可以滑动的六种方法续篇(二)

    承接上一篇,上一篇中讲解了实现滑动的第五种方法,如果你还没读过,可点击下面链接: http://www.cnblogs.com/fuly550871915/p/4985482.html 这篇文章现在来 ...

  8. Android的自定义View及View的绘制流程

    目标:实现Android中的自定义View,为理清楚Android中的View绘制流程“铺路”. 想法很简单:从一个简单例子着手开始编写自定义View,对ViewGroup.View类中与绘制View ...

  9. Android之自定义View学习(一)

    Android之自定义View学习(一) Canvas常用方法: 图片来源 /** * Created by SiberiaDante on 2017/6/3. */ public class Bas ...

随机推荐

  1. extjs中form.reset(true)出现的bug修复

    在之前的开发extjs中,用ext.form.panel开发了一个表单,当使用了reset(true)之后,再次使用getRecord()却还是可以得到值,该值为上一次的旧值,查看了api文档,再结合 ...

  2. GET和POST有什么区别?

    前几天有人问我这个问题.我说GET是用于获取数据的,POST,一般用于将数据发给服务器之用. 这个答案好像并不是他想要的.于是他继续追问有没有别的区别?我说这就是个名字而已,如果服务器支持,他完全可以 ...

  3. GitLab:解决Merge Request中Commits不更新的问题

    最近在使用 GitLab 的 Merge Requests 功能进行 Code Review .操作流程是这样的: 1)开发人员A要给一个项目增加一个新功能,先在这个项目上创建一个 Git 分支. 2 ...

  4. Javascript 异步加载详解(转)

    本文总结一下浏览器在 javascript 的加载方式. 关键词:异步加载(async loading),延迟加载(lazy loading),延迟执行(lazy execution),async 属 ...

  5. js promise 风格编程

    使用q 这种方式,极大的避免了回调地狱的情况产生,以后打算长久用这种方式. 再写Nodejs,再也不担心这个问题了. 以下实例,作为连接数据库的公共方法. /** * Created by Think ...

  6. [游戏学习27] MFC 匀速运动

    >_<:理解上一个时间函数的概念和用法,本节的实现也比较简单 >_<:就是简单的绘图+时间函数 >_<:TicTac.h #define EX 1 //该点左鼠标 ...

  7. Vue学习笔记1

    目录 前言 1.vue和avalon一样,都不支持VM初始时不存在的属性 2.input元素中属性与v-model同时存在以属性为优先 3.VM中的函数放到data属性和methods属性中的区别,以 ...

  8. Git可视化极简易教程 —— Git GUI使用方法

    前言 之前一直想一篇这样的东西,因为最初接触时,我也认真看了廖雪峰的教程,但是似乎我觉得讲得有点多,而且还是会给我带来很多多余且重复的操作负担,所以我希望能压缩一下它在我工作中的成本,但是搜索了一下并 ...

  9. 【Android】混淆器(ProGuard)

    混淆器(ProGuard) 混淆器通过删除从未用过的代码和使用晦涩名字重命名类.字段和方法,对代码进行压缩,优化和混淆.结果是一个比较小的.apk文件,该文件比较难进行逆向工程.因此,当你的应用程序对 ...

  10. paip.重装系统后firefox火狐收藏夹的恢复

    paip.重装系统后firefox火狐收藏夹的恢复 1.使用procmon跟踪ff保存收藏时候的读写文件.. D:\Users\attilax\AppData\Roaming\Mozilla\Fire ...