源碼:html
http://www.2cto.com/kf/201211/171417.htmljava
最近在研究收音機的源碼,原本想把收音機從源碼中提取出來,作成一個單獨的應用,可是,收音機須要底層的支持,因此,就有點無能爲力了,不過發現UI作得比較有個性(以下圖所示),圖片比較醜(這是作圖片MM的責任哦,哈哈),就單獨拿出來跟你們分享一下,順便本身也學習一下自定義view。
今天咱們主要實現的是中間紅色區域的部分,下面咱們來看一下它的佈局文件radio.xml,它最外圍是一個絕對佈局,其實我我的是比較反對用絕對佈局的,用其餘佈局也絕對能解決這個問題,大不了能夠把圖片資源分割一下嗎,可見作這個應用的公司作美工的MM是何等的奇缺啊!O(∩_∩)O哈哈~(可憐的程序猿,又少了一個跟MM相處的機會了)。
<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/background_main"
android:orientation="vertical" >
<RelativeLayout www.2cto.com
android:layout_width="320dip"
android:layout_height="113dip"
android:layout_x="0dip"
android:layout_y="180dip"
android:background="@drawable/led_disabled" />
<RelativeLayout
android:id="@+id/led_background"
android:layout_width="320dip"
android:layout_height="113dip"
android:layout_x="0dip"
android:layout_y="180dip"
android:background="@drawable/led_enabled" >
<ImageView
android:id="@+id/fm_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="14dip"
android:layout_marginTop="12dip"
android:src="@drawable/fm" />
<ImageView
android:id="@+id/headset_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dip"
android:layout_toRightOf="@id/fm_indicator"
android:src="@drawable/headset_indicator" />
<TextView
android:id="@+id/text_hour_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginRight="13dip"
android:layout_marginTop="10dip"
android:textColor="#000000" />
<com.way.view.DigitalTextView
android:id="@+id/digital_clock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginTop="13dip"
android:layout_toLeftOf="@+id/text_hour_type" />
<ImageView
android:id="@+id/searching_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="13dip"
android:layout_toLeftOf="@id/digital_clock"
android:src="@drawable/searching" />
<com.way.view.DigitalTextView
android:id="@+id/digital_freq"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginRight="84dip"
android:layout_marginTop="15dip" />
<SeekBar
android:id="@+id/freq_indicator"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="12dip"
android:layout_marginLeft="7dip"
android:layout_marginRight="7dip"
android:progressDrawable="@drawable/blank"
android:thumb="@drawable/pointer"
android:thumbOffset="0dip" />
</RelativeLayout>
<FrameLayout
android:layout_width="220dip"
android:layout_height="30dip"
android:layout_x="50dip"
android:layout_y="291dip"
android:background="@drawable/scroller_bg" >
<com.way.view.TuneWheel
android:id="@+id/tune_wheel"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/tune_wheel_highlight" />
</FrameLayout>
<ImageButton
android:id="@+id/add_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="39dip"
android:layout_y="332dip"
android:background="#00000000"
android:src="@drawable/add_button" />
<ImageButton
android:id="@+id/prev_station_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="108dip"
android:layout_y="332dip"
android:background="#00000000"
android:src="@drawable/prev_station_button" />
<ImageButton
android:id="@+id/search_prev_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="176dip"
android:layout_y="332dip"
android:background="#00000000"
android:src="@drawable/search_prev_button" />
<com.way.view.CheckableImageButton
android:id="@+id/headset_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="255dip"
android:layout_y="332dip"
android:background="#00000000" />
<com.way.view.CheckableImageButton
android:id="@+id/power_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="0dip"
android:layout_y="403dip"
android:background="#00000000" />
<ImageButton
android:id="@+id/radio_list_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="80dip"
android:layout_y="402dip"
android:background="#00000000"
android:src="@drawable/radio_list_button" />
<ImageButton
android:id="@+id/next_station_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="146dip"
android:layout_y="404dip"
android:background="#00000000"
android:src="@drawable/next_station_button" />
<ImageButton
android:id="@+id/search_next_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="215dip"
android:layout_y="402dip"
android:background="#00000000"
android:src="@drawable/search_next_button" />
</AbsoluteLayout> android
接下來讓咱們來看一下那個自定義滾輪的代碼TuneWheel:
/**
* 調頻的滾輪
*
* @author way
*
*/
public class TuneWheel extends View {
/**
* 滾輪滾動監聽接口
*/
public interface OnTuneWheelValueChangedListener {
public void onTuneWheelValueChanged(View v, float changedBy);
}
private static final String TAG = "TuneWheel";
private static final int LINE_SPAN = 8;//線性跨度
private static final int DRAG_THRESHOLD = 5;//滾動的最小閾值,即在屏幕x軸上至少滑動5個座標點纔有效
private static final float CHANGE_THRESHOLD = 0.1f;//最小的改變閾值,即須要傳遞出去的那個值的最小值
public static final int DIRECTION_PREV = 1;//向左滾動的方向值
public static final int DIRECTION_NEXT = 2;//向右滾動的方向值
private boolean mIsMoving = false;// 是否在滑動
private float mLastDragPos;//最終的滾動位置
private float mCurrentPos = 0;// 當前位置
private float mCurrentChange = 0;// 當前改變值
private int mDragEnable = DIRECTION_PREV | DIRECTION_NEXT;
private OnTuneWheelValueChangedListener mListener = null;// 監聽接口對象
public void setOnValueChangedListener(
OnTuneWheelValueChangedListener listener) {// 提供監聽方法
mListener = listener;
}
/**
* 根據傳入的方向設置是否容許滾動
*
* @param direction
* 傳入的方向
* @param enable
* 是否能滾動
*/
public void setDragEnable(int direction, boolean enable) {
if (enable) {
mDragEnable = mDragEnable | direction;
} else {
mDragEnable = mDragEnable ^ direction;
}
}
/**
* 根據輸入的方向判斷是否容許滾動
*
* @param direction
* 傳入的方向
* @return 是否能滾動
*/
public boolean getDragEnable(int direction) {
return (mDragEnable & direction) != 0;
}
// 三個構造器
public TuneWheel(Context context) {
super(context);
}
public TuneWheel(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TuneWheel(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
// 重寫onDraw方法
protected void onDraw(Canvas canvas) {
Paint mLinePaint = new Paint();
mLinePaint.setARGB(0xEC, 0x00, 0, 0);
mLinePaint.setStrokeWidth(0.5f);
for (int pos = 0; pos + mCurrentPos < getWidth(); pos += LINE_SPAN) {
canvas.drawLine(pos + mCurrentPos, 0, pos + mCurrentPos,
getHeight(), mLinePaint);
}
}
// 重寫onTouchEvent方法,實現滾動效果
public boolean onTouchEvent(MotionEvent event) {
if (!this.isEnabled())
return false;
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN://按下
mLastDragPos = event.getX();//獲取按下時的x軸座標值,滾輪只能左右滑動,因此只要獲取x值便可
break;
case MotionEvent.ACTION_MOVE://移動
float currentDragPos = event.getX();
if (!getDragEnable(getDirection(currentDragPos, mLastDragPos)))//若是當前方向不容許滾動
return false;
if (!mIsMoving) {//若是當前狀態爲沒有滾動
if (Math.abs(currentDragPos - mLastDragPos) > DRAG_THRESHOLD) {//若是滾動範圍大於最小閾值
mIsMoving = true;
mLastDragPos = currentDragPos;
} else {
return false;
}
} else {//若是當前狀態爲一直在滾動
float tempPos = (mCurrentPos + currentDragPos - mLastDragPos)
% LINE_SPAN;//保存一個臨時變化值
mCurrentChange += calculateChange(currentDragPos, mLastDragPos);
if (Math.abs(mCurrentChange) > CHANGE_THRESHOLD) {
if (mListener != null) {
mListener.onTuneWheelValueChanged(this, mCurrentChange);//將滾動變化值經過監聽接口傳遞出去
}
mCurrentChange = 0;
}
mCurrentPos = tempPos;
invalidate();//千萬要記得刷新一下界面
mLastDragPos = currentDragPos;
}
break;
case MotionEvent.ACTION_UP://鬆開
if (mIsMoving) {
mIsMoving = false;//鬆開後更新狀態爲中止滾動0.
}
break;
}
return true;
}
/**
* 根據改變計算滾動方向
*
* @param current
* 當前位置值
* @param last
* 最終位置值
* @return 方向值
*/
private int getDirection(float current, float last) {
return (int) (Math.signum(current - last) + 3) / 2;
}
/**
* 根據改變計算改變值
*
* @param current
* 當前位置值
* @param last
* 最終位置值
* @return 改變值
*/
private float calculateChange(float current, float last) {
float sub = current - last;
return Math.signum(sub) * (float) Math.pow(Math.abs(sub), 1.3) / 100;
}
} git
第三,是中間那個顯示時間和調頻自定義的View,DigitalTextView.java:
/**
* 自定義的一個顯示調頻或時間的view
*
* @author way
*
*/
public class DigitalTextView extends LinearLayout {
private static final String TAG = "DigitalTextView";
private String mResourcePrefix = "";// 資源前綴,時間或調頻
// 兩個構造器,自定義view中至少要有2個構造器
public DigitalTextView(Context context) {
super(context);
init();
}
public DigitalTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
// 初始化
private void init() {
this.setOrientation(LinearLayout.HORIZONTAL);
}
/**
* 獲取對應的時間圖片
*
* @param index
* 時間值
* @return 對應的時間圖片id
*/
private int getTimeDrawable(int index) {
int drawableId = -1;
switch (index) {
case 0:
drawableId = R.drawable.time_0;
break;
case 1:
drawableId = R.drawable.time_1;
break;
case 2:
drawableId = R.drawable.time_2;
break;
case 3:
drawableId = R.drawable.time_3;
break;
case 4:
drawableId = R.drawable.time_4;
break;
case 5:
drawableId = R.drawable.time_5;
break;
case 6:
drawableId = R.drawable.time_6;
break;
case 7:
drawableId = R.drawable.time_7;
break;
case 8:
drawableId = R.drawable.time_8;
break;
case 9:
drawableId = R.drawable.time_9;
break;
}
return drawableId;
}
/**
* 獲取調頻圖片
*
* @param index
* 頻率值
* @return 對應頻率值的圖片id
*/
private int getFreqDrawable(int index) {
int drawableId = -1;
switch (index) {
case 0:
drawableId = R.drawable.freq_0;
break;
case 1:
drawableId = R.drawable.freq_1;
break;
case 2:
drawableId = R.drawable.freq_2;
break;
case 3:
drawableId = R.drawable.freq_3;
break;
case 4:
drawableId = R.drawable.freq_4;
break;
case 5:
drawableId = R.drawable.freq_5;
break;
case 6:
drawableId = R.drawable.freq_6;
break;
case 7:
drawableId = R.drawable.freq_7;
break;
case 8:
drawableId = R.drawable.freq_8;
break;
case 9:
drawableId = R.drawable.freq_9;
break;
}
return drawableId;
}
/**
* 根據傳遞進來的字符,返回對應的圖片資源
*
* @param c
* 傳遞進來的字符
* @return 對應的圖片id
*/
private int getResourceForChar(char c) {
if (c == '.') {
if (!mResourcePrefix.equals("time")) {
return R.drawable.freq_dot;
}
} else if (c == ':') {
if (mResourcePrefix.equals("time")) {
return R.drawable.time_colon;
}
} else if (c >= '0' && c <= '9') {
if (mResourcePrefix.equals("time")) {
return getTimeDrawable(c - '0');
} else {
return getFreqDrawable(c - '0');
}
} else {
return -1;
}
return -1;
}
// 建立一個ImageView
private ImageView createImageView() {
ImageView imageView = new ImageView(getContext());
LayoutParams param = new LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
imageView.setLayoutParams(param);
return imageView;
}
/**
* 設置資源前綴,是時間仍是調頻
*
* @param resourcePrefix
*/
public void setResourcePrefix(String resourcePrefix) {
mResourcePrefix = resourcePrefix;
}
/**
* 設置數字值,相似TextView的setText()
*
* @param text
* 傳遞進來的字符串
*/
public void setDigitalText(String text) {
updateView(text);
}
/**
* 更新自定義TextView
*
* @param text
* 傳遞進來的字符串
*/
private void updateView(String text) {
int startIndex = getChildCount() - text.length();// 起始位置,由於imageView的數量是根據字符串的長度建立的
if (startIndex < 0)//第一次更新的時候確定是小於0的
startIndex = 0;
for (int i = 0; i < startIndex; i++) {
getChildAt(i).setVisibility(View.GONE);//把以前的圖片隱藏起來,我的感受這樣作得不怎麼樣
}
//下面是根據字符串的長度,循環更換爲對應的圖片
for (int i = 0; i < text.length(); i++) {
int childId = i + startIndex;
int resId = getResourceForChar(text.charAt(i));//將每一個字符轉換爲數字
if (resId != -1) {
if (childId == getChildCount()) {
addView(createImageView());//添加到LinearLayout中
}
ImageView child = ((ImageView) getChildAt(childId));
child.setVisibility(View.VISIBLE);
child.setImageResource(resId);
}
}
}
} canvas
最後,就是把這些自定義View綜合起來的MainActivity了:
public class Main extends Activity implements OnTuneWheelValueChangedListener {
private static final int FREQ_POINTER_SCALE = 10;
private TuneWheel mTuneWheel;// 滾輪
private DigitalTextView mFreqView;// 顯示調頻波段的view
private SeekBar mFreqPointer;// 進度條指針
private TextView mClockType;// 顯示時間的格式pm、am
private DigitalTextView mDigitalClock;// 顯示時間的view
private String mClockTypeString;// 時間
private View mSearchingIndicator;// 加載時間的時候刷新圖標
private CheckableImageButton mPowerToggle, mHeadsetToggle;// 開關鍵和耳機揚聲器切換鍵
private DigitalClockUpdater mDigitalClockUpdater;// 更新時間的對象
private float channel = 87.5f;// 默認初始的波段
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.radio);
initView();// 初始化view
}
private void initView() {
// 開關鍵
mPowerToggle = (CheckableImageButton) findViewById(R.id.power_toggle);
mPowerToggle.setDrawable(R.drawable.power_button_checked,
R.drawable.power_button_unchecked,
R.drawable.power_button_disabled);
// 耳機揚聲器切換鍵
mHeadsetToggle = (CheckableImageButton) findViewById(R.id.headset_toggle);
mHeadsetToggle.setDrawable(R.drawable.headset_button_checked,
R.drawable.headset_button_unchecked,
R.drawable.headset_button_disabled);
mClockType = (TextView) findViewById(R.id.text_hour_type);// 時間類型
Date curDate = new Date(java.lang.System.currentTimeMillis());
int hours = curDate.getHours();
if (!DateFormat.is24HourFormat(Main.this)) {// 若是不是24小時制
if (hours > 12) {
mClockTypeString = getString(R.string.pm);
} else {
mClockTypeString = getString(R.string.am);
}
mClockType.setText(mClockTypeString);// 設置pm、am格式
} else {
mClockType.setText("");// 若是所24小時制,就不要任何標識
}
mDigitalClock = (DigitalTextView) findViewById(R.id.digital_clock);
mDigitalClock.setResourcePrefix("time");
mDigitalClockUpdater = new DigitalClockUpdater(mDigitalClock);
mDigitalClockUpdater.run();// 啓動更新時間
mTuneWheel = (TuneWheel) findViewById(R.id.tune_wheel);// 滾輪
mTuneWheel.setOnValueChangedListener(this);// 監聽滾動事件
// 顯示波段的view
mFreqView = (DigitalTextView) findViewById(R.id.digital_freq);
mFreqView.setResourcePrefix("freq");
mFreqView.setDigitalText(String.valueOf(channel));// 初始化爲默認值,實際從配置文件中讀取上次保存的頻段
mSearchingIndicator = findViewById(R.id.searching_indicator);
mSearchingIndicator.setVisibility(View.INVISIBLE);// 加載完時間後,自動隱藏這個圖標
// 頻段進度條
mFreqPointer = (SeekBar) findViewById(R.id.freq_indicator);
// 設置進度條範圍
mFreqPointer
.setMax((int) ((WheelConfig.RADIO_MAX_FREQUENCY - WheelConfig.RADIO_MIN_FREQUENCY) * FREQ_POINTER_SCALE));
mFreqPointer.setEnabled(false);
}
@Override
public void onTuneWheelValueChanged(View v, float changedBy) {// 滾輪滑動監聽事件
// TODO Auto-generated method stub
float freq = adjustFreq(WheelConfig.format(channel + changedBy));// 調整滾輪滑動變化值
channel = freq;// 用一個全局變量保存一下當前的調頻
setFreqForUi(freq);// 更新ui
}
/**
* 調整和限制滾輪滑動的值
*
* @param freq
* 須要調整的值
* @return 調整後的值
*/
private float adjustFreq(float freq) {
float result = ((int) (freq * 10)) / 10f;
if (result < WheelConfig.RADIO_MIN_FREQUENCY)
result = WheelConfig.RADIO_MIN_FREQUENCY;
if (result > WheelConfig.RADIO_MAX_FREQUENCY)
result = WheelConfig.RADIO_MAX_FREQUENCY;
return result;
}
/**
* 經過傳入的滾輪值,更新ui
*
* @param freq
* 調整後的滾輪值
*/
private void setFreqForUi(float freq) {
if (freq == WheelConfig.RADIO_MIN_FREQUENCY) {
mTuneWheel.setDragEnable(TuneWheel.DIRECTION_PREV, false);
mTuneWheel.setDragEnable(TuneWheel.DIRECTION_NEXT, true);
} else if (freq == WheelConfig.RADIO_MAX_FREQUENCY) {
mTuneWheel.setDragEnable(TuneWheel.DIRECTION_PREV, true);
mTuneWheel.setDragEnable(TuneWheel.DIRECTION_NEXT, false);
} else {
mTuneWheel.setDragEnable(TuneWheel.DIRECTION_PREV, true);
mTuneWheel.setDragEnable(TuneWheel.DIRECTION_NEXT, true);
}
// 更新進度條位置
mFreqPointer
.setProgress((int) ((freq - WheelConfig.RADIO_MIN_FREQUENCY) * FREQ_POINTER_SCALE));
mFreqView.setDigitalText(String.valueOf(freq));// 更新當前頻段值
}
@Override
protected void onDestroy() {
super.onDestroy();
mDigitalClockUpdater.stop();// 中止更新時間
}
/**
* 更新時間的類
*
* @author way
*
*/
private class DigitalClockUpdater {
private static final int MSG_UPDATE_TIME = 0x001;
private static final int UPDATE_INTERNAL = 1000;
DigitalTextView mView = null;
boolean mRunning = false;
Handler mUpdateHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_UPDATE_TIME) {
Date curDate = new Date(
java.lang.System.currentTimeMillis());
int hours = curDate.getHours();
int minutes = curDate.getMinutes();
if (DateFormat.is24HourFormat(Main.this)) {
if (minutes < 10) {
mView.setDigitalText("" + hours + ":0" + minutes);
} else {
mView.setDigitalText("" + hours + ":" + minutes);
}
mClockTypeString = "";
} else {
hours = hours > 12 ? hours - 12 : hours;
if (minutes < 10) {
mView.setDigitalText("" + hours + ":0" + minutes);
} else {
mView.setDigitalText("" + hours + ":" + minutes);
}
}
if (mRunning)
this.sendEmptyMessageDelayed(MSG_UPDATE_TIME,
UPDATE_INTERNAL);// 每隔一秒就更新一下時間,保持與系統同步,其實若是隻須要每分鐘更新時間,能夠經過接收系統廣播同步,這個廣播每分鐘發送一次。
}
}
};
public DigitalClockUpdater(DigitalTextView view) {
mView = view;
}
public void run() {
mRunning = true;
mUpdateHandler.removeMessages(MSG_UPDATE_TIME);
mUpdateHandler.sendEmptyMessage(MSG_UPDATE_TIME);
}
public void stop() {
mRunning = false;
mView = null;
mUpdateHandler.removeMessages(MSG_UPDATE_TIME);
}
}
}<span style="font-size:18px;">
</span> ide