Andorid之自定义控件②
关于自定义控件上一篇文章说了,自定义控件之组合控件。
接下来我们说说自绘控件。
说下说下自绘控件的步骤
第一步、自定义View的属性
第二步、在View的构造方法中获得我们自定义的属性
第三步、重写onMesure
第四步、重写onDraw
其中,第一,二,四步是基本步骤必不可少,第三步非必须步骤。
自定义View的属性
自定义属性有两种方法:
第一种
首先在res/values/ 下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。
可以定义的属性有
<declare-styleable name = "XX">
//参考某一资源ID (XX可以随便命名)
<attr name = "background" format = "reference" />
//颜色值
<attr name = "textColor" format = "color" />
//布尔值
<attr name = "focusable" format = "boolean" />
//尺寸值
<attr name = "layout_width" format = "dimension" />
//浮点值
<attr name = "fromAlpha" format = "float" />
//整型值
<attr name = "frameDuration" format="integer" />
//字符串
<attr name = "text" format = "string" />
//百分数
<attr name = "pivotX" format = "fraction" />
//枚举值
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
//位或运算
<attr name="windowSoftInputMode">
<flag name = "stateUnspecified" value = "0" />
<flag name = "stateUnchanged" value = "1" />
</attr>
//多类型
<attr name = "background" format = "reference|color" />
</declare-styleable>
第二种
在布局文件中直接加入属性,在构造函数中去获得。
最后
注意命名空间:
xmlns:xx=”http://schemas.android.com/apk/res/xxx.xx.xxx(或res-auto)”,
在View的构造方法中获得我们自定义的属性
CustomView的构造方法中通过TypedArray获取
在View的构造方法中,获得我们的自定义的样式
重写onMesure ,onDraw
主要是重写onDraw方法。
总结
第一步,声明自定义控件的属性(声明控件)
第二步,继承View并复写部分函数(实现控件)
第三步,设置一个自定义控件的容器界面(引用控件)
最后
做了一个小小的demo 先看看效果:
可以围绕中心位置在一定范围内上下左右滑动或者通过控件控制上下左右移动
废话不多说了,直接上代码了
在资源文件夹下values文件夹下新建attrs.xml文件
</declare-styleable>
<declare-styleable name="BV">
<attr name="balance_min_x" format="dimension"/>
<attr name="balance_min_y" format="dimension"/>
<attr name="balance_max_x" format="dimension"/>
<attr name="balance_max_y" format="dimension"/>
<attr name="balance_min_value" format="integer"/>
<attr name="balance_max_value" format="integer"/>
<attr name="balance_bg" format="reference"/>
<attr name="balance_block" format="reference"/>
</declare-styleable>
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.BitmapFactory.Options;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
public class BalanceView extends View {
private static final String TAG = "BalanceView";
private Context mContext;
// X坐标最小值,Y坐标最小值,X坐标最大值,Y坐标最大值,最大移动宽度,最大移动高度;
private int mMinX = 0, mMinY = 0, mMaxX = 0, mMaxY = 0, mMoveWidth = 0, mMoveHeight = 0,
//最小值,最大值,滑块宽度,滑块高度,背景宽度,背景高度,左右值,前后值;
mMinValue = 0,mMaxValue = 0, mBlockWidth = 0,mBlockHeight = 0, mBgWidth = 0,mBgHeight = 0,mLRValue = 0, mFRValue = 0,mLayoutWidth = 0, mLayoutHeight = 0, mBgX = 0, mBgY = 0,
mBlockX = 0, mBlockY = 0;
private int centerBtnWidth = 50, centerBtnHeight = 50;
private int mBmapLineH_Width, mBmapLineS_height;
private Bitmap mBmpBg, mBmpBlock, mBmapLineH, mBmapLineS;
private boolean mCanUpdate = true, mFirstTime = true;
private float mDownX = 0, mDownY = 0;
private Bitmap mBmpLightLeftUp, mBmpLightLeftDown, mBmpLightRight;
public BalanceView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.e(TAG, "BalanceView()");
//获取自定义属性的数组
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.BV);
int minX = (int) ta.getDimensionPixelOffset(R.styleable.BV_balance_min_x, 0);
int minY = ta.getDimensionPixelOffset(R.styleable.BV_balance_min_y, 0);
int maxX = (int) ta.getDimensionPixelOffset(R.styleable.BV_balance_max_x, 0);
int maxY = ta.getDimensionPixelOffset(R.styleable.BV_balance_max_y, 0);
int minValue = ta.getInt(R.styleable.BV_balance_min_value, mMinValue);
int maxValue = ta.getInt(R.styleable.BV_balance_max_value, mMaxValue);
int bgId = ta.getResourceId(R.styleable.BV_balance_bg, 0);
int blockId = ta.getResourceId(R.styleable.BV_balance_block, 0);
//释放资源
ta.recycle();
mMinX = minX;
mMinY = minX;
mMaxX = maxX;
mMaxY = maxY;
mMinValue = minValue;
mMaxValue = maxValue;
Log.e(TAG, "mMinX=" + mMinX + ",mMinY=" + mMinY + ",mMaxX+" + mMaxX + ",mMaxY=" + mMaxY + ",mMinValue="
+ mMinValue + ",mMaxValue=" + mMaxValue);
Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inScaled = false;
if (bgId != 0) {
mBmpBg = BitmapFactory.decodeResource(getResources(), bgId, bitmapOptions);
}
if (blockId != 0) {
mBmpBlock = BitmapFactory.decodeResource(getResources(), blockId, bitmapOptions);
mBmapLineH = BitmapFactory.decodeResource(getResources(), R.drawable.volume_hline, bitmapOptions);
mBmapLineS = BitmapFactory.decodeResource(getResources(), R.drawable.volume_vline, bitmapOptions);
mBmapLineH_Width = mBmapLineH.getWidth();
mBmapLineS_height = mBmapLineS.getHeight();
Log.d(TAG, "mBmapLineH_Width="+mBmapLineH_Width+",mBmapLineS_height="+mBmapLineS_height);
init(context);
}
}
public void UpdateBalance() {
Log.d(TAG, "UpdateBalance()");
if (!mCanUpdate)
return;
if (mMoveWidth <= 0)
return;
if (mMoveHeight <= 0)
return;
if (mMaxValue - mMinValue <= 0)
return;
int[] balanceValue = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
mFRValue = balanceValue[1];
Log.d(TAG, "LR=" + balanceValue[0] + ",FR=" + balanceValue[1]);
mLRValue = mMaxValue - balanceValue[0] + mMinValue;
float x = mMoveWidth * (mFRValue - mMinValue) / (mMaxValue - mMinValue) + mMinX;
float y = mMoveHeight * (mLRValue - mMinValue) / (mMaxValue - mMinValue) + mMinY;
SetBlockPosOnly(x, y);
}
public void SetFRValue(int value) {
Log.d(TAG, "SetFRValue(),value=" + value);
if (value < mMinValue || value > mMaxValue)
return;
mFRValue = value;
setLocationByData(value, mLRValue);
}
public void SetLRValue(int value) {
Log.d(TAG, "SetLRValue(),value=" + value);
if (value < mMinValue || value > mMaxValue)
return;
setLocationByData(mFRValue, value);
}
public int GetFRValue() {
Log.d(TAG, "GetFRValue(),mFRValue=" + mFRValue);
return mFRValue;
}
public int GetLRValue() {
Log.d(TAG, "GetLRValue(),mLRValue=" + mLRValue);
return mLRValue;
}
public void setBlockDrable(Drawable drawable) {
Log.d(TAG, "setBlockDrable()");
mBmpBlock = ((BitmapDrawable) drawable).getBitmap();
invalidate();
}
public void setLineH(Drawable drawable) {
Log.d(TAG, "setLineH()");
mBmapLineH = ((BitmapDrawable) drawable).getBitmap();
invalidate();
}
public void setLines(Drawable drawable) {
Log.d(TAG, "setLines()");
mBmapLineS = ((BitmapDrawable) drawable).getBitmap();
invalidate();
}
private void setLocationByData(int fr, int lr) {
Log.d(TAG, "setLocationByData(),fr=" + fr + ",lr=" + lr);
mFRValue = fr;
mLRValue = lr;
float x = mMoveWidth * (mFRValue - mMinValue) / (mMaxValue - mMinValue) + mMinX;
float y = mMoveHeight * (mLRValue - mMinValue) / (mMaxValue - mMinValue) + mMinY;
SetBlockPosOnly(x, y);
}
private boolean isCenterBtn(int x, int y) {
Log.d(TAG, "isCenterBtn(),x=" + x + ",y=" + y);
if (((mLayoutWidth - centerBtnWidth) / 2 <= x && (mLayoutHeight + centerBtnWidth) / 2 >= x)
&& ((mLayoutHeight - centerBtnHeight) / 2 <= y && (mLayoutHeight + centerBtnHeight) / 2 >= y))
return true;
return false;
}
//初始化控件
private void init(Context context) {
Log.e(TAG, "init()");
mContext = context;
mBgWidth = 350;
mBgHeight = 230;
mLayoutWidth = mBgWidth;
mLayoutHeight = mBgHeight;
//计算控件尺寸
if (mBmpBlock != null) {
mBlockWidth = mBmpBlock.getWidth();
mBlockHeight = mBmpBlock.getHeight();
}
mMoveWidth = (int) (mMaxX - mMinX);
mMoveHeight = (int) (mMaxY - mMinY);
//横向移动宽度超出背景宽度
if (mMoveWidth > mBgWidth) {
//设置背景居中
mBgX = (mMoveWidth - mBgWidth) / 2;
mLayoutWidth = mMoveWidth;
} else if (mMoveWidth == 0) {//没有移动
mMoveWidth = mBgWidth;
mMinX = 0;
mMaxX = mMoveWidth;
} else if (mMoveWidth < mBgWidth) {//移动范围小于背景宽度
int temp = mBlockWidth / 2;
mMinX += temp;//调整移动范围
mMaxX += temp;//调整移动范围
}
if (mMoveHeight > mBgHeight) {
mBgY = (mMoveHeight - mBgHeight) / 2;
mLayoutHeight = mMoveHeight;
} else if (mMoveHeight == 0) {
mMoveHeight = mBgHeight;
mMinY = 0;
mMaxY = mMoveHeight;
} else if (mMoveHeight < mBgHeight) {
int temp = (mBgHeight - mMoveHeight) / 2;
mMinY += temp;
mMaxY += temp;
}
//滑块必须内嵌在移动范围内
mMoveWidth -= mBlockWidth;
mMoveHeight -= mBlockHeight;
mMinX += mBlockWidth / 2;
mMaxX -= mBlockWidth / 2;
mMinY += mBlockHeight / 2;
mMaxY -= mBlockHeight / 2;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
//设置点击是否有效
boolean touchValid = false;
Log.d(TAG, "onTouchEvent(),x=" + x + ",y=" + y);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mFirstTime = true;
mDownX = x;
mDownY = y;
mCanUpdate = false;
SetBlockPos(x, y);
touchValid = true;
break;
case MotionEvent.ACTION_MOVE:
if (mDownX == x && mDownY == y && mFirstTime)
return false;
mFirstTime = false;
SetBlockPos(x, y);
touchValid = true;
break;
case MotionEvent.ACTION_UP:
mCanUpdate = true;
SetBlockPos(x, y);
touchValid = true;
break;
default:
break;
}
return touchValid;
// return super.onTouchEvent(event);
}
/**
* 设置滑块位置
*
* @param x
* @param y
*/
public void SetBlockPos(int x, int y) {
Log.d(TAG, "SetBlockPos(),x=" + x + "y=" + y + ",mMinX=" + mMinX + "mMaxX=" + mMaxX + ",mMinY=" + mMinY
+ ",mMaxY=" + mMaxY);
if (mMoveWidth <= 0) {
return;
}
if (mMoveHeight <= 0) {
return;
}
if (mMaxValue - mMinValue <= 0) {
return;
}
if (x < mMinX) {
x = mMinX;
} else if (x > mMaxX) {
x = mMaxX;
}
if (y < mMinY) {
y = mMinY;
} else if (y > mMaxY) {
y = mMaxY;
}
SetBlockPosOnly(x, y);
// 更新位置
mLRValue = ((mMaxValue - mMinValue) * (y - mMinY) / mMoveHeight + mMinValue);
mFRValue = ((mMaxValue - mMinValue) * (x - mMinX) / mMoveWidth + mMinValue);
}
private void SetBlockPosOnly(float x, float y) {
Log.d(TAG, "SetBlockPosOnly(),x=" + x + ",y=" + y);
if (mMoveWidth <= 0)
return;
if (mMoveHeight <= 0)
return;
if (mMaxValue - mMinValue <= 0)
return;
if (x < mMinX) {
x = mMinX;
} else if (x > mMaxX) {
x = mMaxX;
}
if (y < mMinY) {
y = mMinY;
} else if (y > mMaxY) {
y = mMaxY;
}
mBlockX = (int) (x - mBlockWidth / 2);
mBlockY = (int) (y - mBlockHeight / 2);
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画图
if (mBmpBg != null) canvas.drawBitmap(mBmpBg, mBgX, mBgY, null);
if (mBlockX < (mBgWidth - mBlockWidth) / 2 && mBlockY < (mBgHeight - mBlockHeight) / 2) {
if (mBmpLightLeftUp != null) {
canvas.drawBitmap(mBmpLightLeftUp, 0, 0, null);
}
}
if (mBlockX < (mBgWidth - mBlockWidth) / 2 && mBlockY > (mBgHeight - mBlockHeight) / 2) {
if (mBmpLightLeftDown != null) {
canvas.drawBitmap(mBmpLightLeftDown, 0, 0, null);
}
}
if (mBlockX > (mBgWidth - mBlockWidth) / 2) {
if (mBmpLightRight != null) {
canvas.drawBitmap(mBmpLightRight, 0, 0, null);
}
}
if (mBmapLineH != null) {
canvas.drawBitmap(mBmapLineH, mBlockX - (mBmapLineH.getWidth() - mBlockWidth) / 2,
mBlockY + mBlockHeight / 2 - 2, null);
}
if (mBmapLineS != null) {
canvas.drawBitmap(mBmapLineS, mBlockX + mBlockWidth / 2 - 2, mBlockY - (mBmapLineS.getHeight() -
mBlockHeight) / 2,null);
}
if(mBmpBlock != null){
canvas.drawBitmap(mBmpBlock, mBlockX, mBlockY,null);
}
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:mv="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res/com.tencent.myvieweg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#000000"
tools:context="com.tencent.myvieweg.MainActivity" >
<LinearLayout
android:id="@+id/ll_reset_center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_weight="1"
android:gravity="right"
android:orientation="horizontal"
android:paddingRight="40px" >
<ImageView
android:id="@+id/img_reset_center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/voice_balance_center" />
<TextView
android:id="@+id/tv_balance_center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="20px"
android:textColor="@android:color/white"
android:text="复位"
android:textSize="30px" />
</LinearLayout>
<FrameLayout
android:layout_width="725px"
android:layout_height="402px"
android:layout_gravity="center_horizontal"
android:background="@drawable/bgone" >
<com.tencent.myvieweg.BalanceView
android:id="@+id/bv_balance"
android:layout_width="344dp"
android:layout_height="225dp"
android:layout_marginLeft="234px"
android:layout_marginTop="88px"
android:alpha="0.7"
app:balance_block="@drawable/locate_circle"
app:balance_max_value="18"
app:balance_min_value="0" />
<ImageView
android:id="@+id/lv_left_reduce_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="154px"
android:background="@drawable/voice_balance_l_selector" />
<ImageView
android:id="@+id/lv_top_add_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginLeft="370px"
android:background="@drawable/voice_balance_t_selector" />
<ImageView
android:id="@+id/lv_bottom_reduce_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginLeft="370px"
android:background="@drawable/voice_balance_b_selector" />
<ImageView
android:id="@+id/lv_right_add_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="60px"
android:background="@drawable/voice_balance_r_selector" />
</FrameLayout>
</LinearLayout>
控件的按下效果和资源文件问简单就没有贴上来了;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
public class MainActivity extends Activity implements OnClickListener{
private static final String TAG = "VMainActivity";
public LinearLayout mLl_reset_center;
public BalanceView mBv_balance;
public ImageView mLv_left_reduce_img,mLv_top_add_img,mLv_bottom_reduce_img,mLv_right_add_img;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
mLl_reset_center = (LinearLayout) findViewById(R.id.ll_reset_center);
mBv_balance = (BalanceView) findViewById(R.id.bv_balance);
mLv_left_reduce_img = (ImageView) findViewById(R.id.lv_left_reduce_img);
mLv_top_add_img = (ImageView) findViewById(R.id.lv_top_add_img);
mLv_bottom_reduce_img = (ImageView) findViewById(R.id.lv_bottom_reduce_img);
mLv_right_add_img = (ImageView) findViewById(R.id.lv_right_add_img);
mLl_reset_center.setOnClickListener(this);
mBv_balance .setOnClickListener(this);
mLv_left_reduce_img .setOnClickListener(this);
mLv_top_add_img .setOnClickListener(this);
mLv_bottom_reduce_img .setOnClickListener(this);
mLv_right_add_img .setOnClickListener(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.ll_reset_center:
Log.e(TAG, "onClick(),ll_reset_center");
mBv_balance.SetBlockPos(175, 115);
break;
case R.id.lv_left_reduce_img:
mBv_balance.SetFRValue(mBv_balance.GetFRValue() - 1);
break;
case R.id.lv_top_add_img:
mBv_balance.SetLRValue(mBv_balance.GetLRValue() - 1);
break;
case R.id.lv_right_add_img:
mBv_balance.SetFRValue(mBv_balance.GetFRValue() + 1);
break;
case R.id.lv_bottom_reduce_img:
mBv_balance.SetLRValue(mBv_balance.GetLRValue() + 1);
break;
default:
break;
}
}
}