Android之收音机UI实现

最近在研究收音机的源码,本来想把收音机从源码中提取出来,做成一个单独的应用,但是,收音机需要底层的支持,所以,就有点无能为力了,不过发现UI做得比较有个性(如下图所示),图片比较丑(这是做图片MM的责任哦,哈哈),就单独拿出来跟大家分享一下,顺便自己也学习一下自定义view。

代码下载:http://download.csdn.net/detail/weidi1989/4782390


今天我们主要实现的是中间红色区域的部分,下面我们来看一下它的布局文件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
        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>

接下来让我们来看一下那个自定义滚轮的代码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;
	}
}


第三,是中间那个显示时间和调频自定义的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);
			}
		}
	}
}

最后,就是把这些自定义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);
		}
	}
}

OK,大功告成,总结一下:①.从别人自定义view中学到一些关键部分,提取出来并熟练使用。②.也不是建议什么view都建议用自定义的,比如说,这个代码中那个开关按钮和耳机切换按钮,完全没必要用自定义view,也可以实现的。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值