1. 程式人生 > >安卓開發-高仿ios時間選擇控制元件timepicker

安卓開發-高仿ios時間選擇控制元件timepicker

在開發中如果有地址或者日期選擇等就會涉及到時間或者條件選擇器,大家都會想到仿ios的三級聯動的效果,用wheelview實現,其實安卓原生自帶了時間和日期選擇器可能是效果來說太粗獷了,所以很多產品效果圖都是清一色的ios那種效果,ok,廢話說完了上圖


demo地址:https://github.com/PangHaHa12138/TimePackdemo

1,安卓原生的時間和日期選擇器

程式碼:

DatePickerDialog dialog = new
DatePickerDialog(MainActivity.this, new DatePickerDialog.OnDateSetListener() {
            @Override
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { if(monthOfYear<=9){ mouth1="0"+(monthOfYear+1); }else{ mouth1=String.valueOf(monthOfYear+1); } if(dayOfMonth<=9){ day1
= "0"+dayOfMonth; }else{ day1=String.valueOf(dayOfMonth); } dateStr = String.valueOf(year)+"-"+mouth1+"-"+day1; button1.setText(dateStr); } }, calender.get(Calendar.YEAR), calender.get(Calendar.MONTH), calender.get(Calendar.DAY_OF_MONTH)); dialog.show();
使用非常簡單,自己寫個時間過濾方法就行
TimePickerDialog dialog = new 
TimePickerDialog(MainActivity.this, new TimePickerDialog.OnTimeSetListener() { @Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { button2.setText(String.valueOf(hourOfDay+":"+minute)); } },calender2.get(Calendar.HOUR),calender2.get(Calendar.MINUTE),false); dialog.show();
DatePickerDialog和TimePickerDialog都是原生帶的,在5.0之前還是和ios那種效果差不多,來回上下滑選擇日期的,但是5.0後就成日曆了,雖然好看,不過佔比屏太大,設計並不會考慮這種效果,哎,現在很多安卓的控制元件原生其實效果不錯的,不過還是得為了遵循產品和設計的意思做成仿ios效果(小小的抱怨一下)

2.開源日期選擇器Timepickview和條件選擇器Optionspickview

程式碼:timepick:

 TimePickerView pvTime = new TimePickerView.Builder(MainActivity.this, new TimePickerView.OnTimeSelectListener() {
                    @Override
public void onTimeSelect(Date date2, View v) {//選中事件回撥
String time = getTime(date2);
button3.setText(time);
}
                })
                        .setType(TimePickerView.Type.YEAR_MONTH_DAY)//預設全部顯示
.setCancelText("取消")//取消按鈕文字
.setSubmitText("確定")//確認按鈕文字
.setContentSize(20)//滾輪文字大小
.setTitleSize(20)//標題文字大小
//                        .setTitleText("請選擇時間")//標題文字
.setOutSideCancelable(true)//點選螢幕,點在控制元件外部範圍時,是否取消顯示
.isCyclic(true)//是否迴圈滾動
.setTextColorCenter(Color.BLACK)//設定選中項的顏色
.setTitleColor(Color.BLACK)//標題文字顏色
.setSubmitColor(Color.BLUE)//確定按鈕文字顏色
.setCancelColor(Color.BLUE)//取消按鈕文字顏色
//                        .setTitleBgColor(0xFF666666)//標題背景顏色 Night mode
//                        .setBgColor(0xFF333333)//滾輪背景顏色 Night mode
//                        .setRange(calendar.get(Calendar.YEAR) - 20, calendar.get(Calendar.YEAR) + 20)//預設是1900-2100//                        .setDate(selectedDate)// 如果不設定的話,預設是系統時間*/
//                        .setRangDate(startDate,endDate)//起始終止年月日設定
//                        .setLabel("","","","","","")
.isCenterLabel(false) //是否只顯示中間選中項的label文字,false則每項item全部都帶有label//                        .isDialog(true)//是否顯示為對話方塊樣式
.build();
pvTime.setDate(Calendar.getInstance());//注:根據需求來決定是否使用該方法(一般是精確到秒的情況),此項可以在彈出選擇器的時候重新設定當前時間,避免在初始化之後由於時間已經設定,導致選中時間與當前時間不匹配的問題。
pvTime.show();
註釋已經寫的很詳細了,各種選項幹嘛用的

Optionspickview:程式碼:

options1Items.clear();
options1Items.add("托兒索");
options1Items.add("兒童劫");
options1Items.add("小學生之手");
options1Items.add("德瑪西亞大保健");
options1Items.add("面對疾風吧");
options1Items.add("天王蓋地虎");
options1Items.add("我發一米五");
options1Items.add("爆劉繼芬");

OptionsPickerView pvOptions = new  OptionsPickerView.Builder(MainActivity.this, new OptionsPickerView.OnOptionsSelectListener() {
                    @Override
public void onOptionsSelect(int options1, int option2, int options3 ,View v) {
                        //返回的分別是三個級別的選中位置
String s =  options1Items.get(options1);
button4.setText(s);
}
                })
//                        .setSubmitText("確定")//確定按鈕文字
//                        .setCancelText("取消")//取消按鈕文字
//                        .setTitleText("城市選擇")//標題
.setSubCalSize(20)//確定和取消文字大小
//                        .setTitleSize(20)//標題文字大小
//                        .setTitleColor(Color.BLACK)//標題文字顏色
.setSubmitColor(Color.BLUE)//確定按鈕文字顏色
.setCancelColor(Color.BLUE)//取消按鈕文字顏色
//                        .setTitleBgColor(0xFF333333)//標題背景顏色 Night mode
//                        .setBgColor(0xFF000000)//滾輪背景顏色 Night mode
//                        .setContentTextSize(18)//滾輪文字大小
//                        .setTextColorCenter(Color.BLUE)//設定選中項的顏色
.setTextColorCenter(Color.BLACK)//設定選中項的顏色
//                        .setLineSpacingMultiplier(1.6f)//設定兩橫線之間的間隔倍數
//                        .setLinkage(false)//設定是否聯動,預設true
//                        .setLabels("", "", "")//設定選擇的三級單位
//                        .isCenterLabel(false) //是否只顯示中間選中項的label文字,false則每項item全部都帶有label//                        .setCyclic(false, false, false)//迴圈與否
//                        .setSelectOptions(1, 1, 1)  //設定預設選中項
//                        .setOutSideCancelable(false)//點選外部dismiss default true
//                        .isDialog(true)//是否顯示為對話方塊樣式
.build();
pvOptions.setPicker(options1Items);
pvOptions.show();
條件自己定義,可以傳簡單集合,也可以傳別的,比如省市縣...

3.結合wheelview和原生timepick自定義時間選擇器

用法:

final String[] str = new String[10];
ChangeDatePopwindow mChangeBirthDialog = new ChangeDatePopwindow(MainActivity.this);
mChangeBirthDialog.setDate("2017", "6", "20");
mChangeBirthDialog.showAtLocation(main, Gravity.BOTTOM, 0, 0);
mChangeBirthDialog.setBirthdayListener(new ChangeDatePopwindow.OnBirthListener() {

    @Override
public void onClick(String year, String month, String day) {
        // TODO Auto-generated method stub
Toast.makeText(MainActivity.this,year + "-" + month + "-" + day,Toast.LENGTH_LONG).show();
StringBuilder sb = new StringBuilder();
sb.append(year.substring(0, year.length() - 1)).append("-").append(month.substring(0, day.length() - 1)).append("-").append(day);
str[0] = year + "-" + month + "-" + day;
str[1] = sb.toString();
button5.setText(str[0]);
}
});
大致思路:

年-月-日,其實是三個wheelview,然後手動設定時間的範圍,繼承原有的adapter

一個例子

 * Abstract wheel adapter provides common functionality for adapters.
 */
public abstract class AbstractWheelTextAdapter extends AbstractWheelAdapter {
    
    /** Text view resource. Used as a default view for adapter. */
public static final int TEXT_VIEW_ITEM_RESOURCE = -1;
/** No resource constant. */
protected static final int NO_RESOURCE = 0;
/** Default text color */
public static final int DEFAULT_TEXT_COLOR = 0xFF585858;
/** Default text color */
public static final int LABEL_COLOR = 0xFF700070;
/** Default text size */
public static final int DEFAULT_TEXT_SIZE = 18;
// Text settings
private int textColor = DEFAULT_TEXT_COLOR;
    private int textSize = DEFAULT_TEXT_SIZE;
// Current context
protected Context context;
// Layout inflater
protected LayoutInflater inflater;
// Items resources
protected int itemResourceId;
    protected int itemTextResourceId;
// Empty items resources
protected int emptyItemResourceId;
/**
     * Constructor
     * @param context the current context
     */
protected AbstractWheelTextAdapter(Context context) {
        this(context, TEXT_VIEW_ITEM_RESOURCE);
}

    /**
     * Constructor
     * @param context the current context
     * @param itemResource the resource ID for a layout file containing a TextView to use when instantiating items views
     */
protected AbstractWheelTextAdapter(Context context, int itemResource) {
        this(context, itemResource, NO_RESOURCE);
}
    
    /**
     * Constructor
     * @param context the current context
     * @param itemResource the resource ID for a layout file containing a TextView to use when instantiating items views
     * @param itemTextResource the resource ID for a text view in the item layout
     */
protected AbstractWheelTextAdapter(Context context, int itemResource, int itemTextResource) {
        this.context = context;
itemResourceId = itemResource;
itemTextResourceId = itemTextResource;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
    
    /**
     * Gets text color
     * @return the text color
     */
public int getTextColor() {
        return textColor;
}
    
    /**
     * Sets text color
     * @param textColor the text color to set
     */
public void setTextColor(int textColor) {
        this.textColor = textColor;
}
    
    /**
     * Gets text size
     * @return the text size
     */
public int getTextSize() {
        return textSize;
}
    
    /**
     * Sets text size
     * @param textSize the text size to set
     */
public void setTextSize(int textSize) {
        this.textSize = textSize;
}
    
    /**
     * Gets resource Id for items views
     * @return the item resource Id
     */
public int getItemResource() {
        return itemResourceId;
}
    
    /**
     * Sets resource Id for items views
     * @param itemResourceId the resource Id to set
     */
public void setItemResource(int itemResourceId) {
        this.itemResourceId = itemResourceId;
}
    
    /**
     * Gets resource Id for text view in item layout 
     * @return the item text resource Id
     */
public int getItemTextResource() {
        return itemTextResourceId;
}
    
    /**
     * Sets resource Id for text view in item layout 
     * @param itemTextResourceId the item text resource Id to set
     */
public void setItemTextResource(int itemTextResourceId) {
        this.itemTextResourceId = itemTextResourceId;
}

    /**
     * Gets resource Id for empty items views
     * @return the empty item resource Id
     */
public int getEmptyItemResource() {
        return emptyItemResourceId;
}

    /**
     * Sets resource Id for empty items views
     * @param emptyItemResourceId the empty item resource Id to set
     */
public void setEmptyItemResource(int emptyItemResourceId) {
        this.emptyItemResourceId = emptyItemResourceId;
}
    
    
    /**
     * Returns text for specified item
     * @param index the item index
     * @return the text of specified items
     */
protected abstract CharSequence getItemText(int index);
@Override
public View getItem(int index, View convertView, ViewGroup parent) {
        if (index >= 0 && index < getItemsCount()) {
            if (convertView == null) {
                convertView = getView(itemResourceId, parent);
}
            TextView textView = getTextView(convertView, itemTextResourceId);
            if (textView != null) {
                CharSequence text = getItemText(index);
                if (text == null) {
                    text = "";
}
                textView.setText(text);
                if (itemResourceId == TEXT_VIEW_ITEM_RESOURCE) {
                    configureTextView(textView);
}
            }
            return convertView;
}
       return null;
}

    @Override
public View getEmptyItem(View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = getView(emptyItemResourceId, parent);
}
        if (emptyItemResourceId == TEXT_VIEW_ITEM_RESOURCE && convertView instanceof TextView) {
            configureTextView((TextView)convertView);
}
            
        return convertView;
}

    /**
     * Configures text view. Is called for the TEXT_VIEW_ITEM_RESOURCE views.
     * @param view the text view to be configured
     */
protected void configureTextView(TextView view) {
        view.setTextColor(textColor);
view.setGravity(Gravity.CENTER);
view.setTextSize(textSize);
view.setEllipsize(TextUtils.TruncateAt.END);
view.setLines(1);
//        view.setCompoundDrawablePadding(20);
//        view.setTypeface(Typeface.SANS_SERIF, Typeface.BOLD);
}
    
    /**
     * Loads a text view from view
     * @param view the text view or layout containing it
     * @param textResource the text resource Id in layout
     * @return the loaded text view
     */
public TextView getTextView(View view, int textResource) {
       TextView text = null;
       try {
            if (textResource == NO_RESOURCE && view instanceof TextView) {
                text = (TextView) view;
} else if (textResource != NO_RESOURCE) {
                text = (TextView) view.findViewById(textResource);
}
        } catch (ClassCastException e) {
            Log.e("AbstractWheelAdapter", "You must supply a resource ID for a TextView");
            throw new IllegalStateException(
                    "AbstractWheelAdapter requires the resource ID to be a TextView", e);
}
        
        return text;
}
    
    /**
     * Loads view from resources
     * @param resource the resource Id
     * @return the loaded view or null if resource is not set
     */
public View getView(int resource, ViewGroup parent) {
        switch (resource) {
        case NO_RESOURCE:
            return null;
        case TEXT_VIEW_ITEM_RESOURCE:
            return new TextView(context);
        default:
            return inflater.inflate(resource, parent, false);    
}
    }

還有設定滑動監聽,改變日期

wvYear.addChangingListener(new OnWheelChangedListener() {

   @Override
public void onChanged(WheelView wheel, int oldValue, int newValue) {
      // TODO Auto-generated method stub
String currentText = (String) mYearAdapter.getItemText(wheel.getCurrentItem());
selectYear = currentText;
setTextviewSize(currentText, mYearAdapter);
currentYear = currentText.substring(0, currentText.length()-1).toString();
Log.d("currentYear==",currentYear);
setYear(currentYear);
initMonths(Integer.parseInt(month));
mMonthAdapter = new CalendarTextAdapter(context, arry_months, 0, maxTextSize, minTextSize);
wvMonth.setVisibleItems(5);
wvMonth.setViewAdapter(mMonthAdapter);
wvMonth.setCurrentItem(0);
calDays(currentYear, month);
}
});
wvYear.addScrollingListener(new OnWheelScrollListener() {

   @Override
public void onScrollingStarted(WheelView wheel) {
      // TODO Auto-generated method stub
}

   @Override
public void onScrollingFinished(WheelView wheel) {
      // TODO Auto-generated method stub
String currentText = (String) mYearAdapter.getItemText(wheel.getCurrentItem());
setTextviewSize(currentText, mYearAdapter);
}
});
wvMonth.addChangingListener(new OnWheelChangedListener() {

   @Override
public void onChanged(WheelView wheel, int oldValue, int newValue) {
      // TODO Auto-generated method stub
String currentText = (String) mMonthAdapter.getItemText(wheel.getCurrentItem());
selectMonth = currentText;
setTextviewSize(currentText, mMonthAdapter);
setMonth(currentText.substring(0, 1));
initDays(Integer.parseInt(day));
mDaydapter = new CalendarTextAdapter(context, arry_days, 0, maxTextSize, minTextSize);
wvDay.setVisibleItems(5);
wvDay.setViewAdapter(mDaydapter);
wvDay.setCurrentItem(0);
calDays(currentYear, month);
}
});
wvMonth.addScrollingListener(new OnWheelScrollListener() {

   @Override
public void onScrollingStarted(WheelView wheel) {
      // TODO Auto-generated method stub
}

   @Override
public void onScrollingFinished(WheelView wheel) {
      // TODO Auto-generated method stub
String currentText = (String) mMonthAdapter.getItemText(wheel.getCurrentItem());
setTextviewSize(currentText, mMonthAdapter);
}
});
wvDay.addChangingListener(new OnWheelChangedListener() {

   @Override
public void onChanged(WheelView wheel, int oldValue, int newValue) {
      // TODO Auto-generated method stub
String currentText = (String) mDaydapter.getItemText(wheel.getCurrentItem());
setTextviewSize(currentText, mDaydapter);
selectDay = currentText;
}
});
wvDay.addScrollingListener(new OnWheelScrollListener() {

   @Override
public void onScrollingStarted(WheelView wheel) {
      // TODO Auto-generated method stub
}

   @Override
public void onScrollingFinished(WheelView wheel) {
      // TODO Auto-generated method stub
String currentText = (String) mDaydapter.getItemText(wheel.getCurrentItem());
setTextviewSize(currentText, mDaydapter);
}
});

然後彈出的彈窗popuwindows裡,設定時間

public String getYear() {
   Calendar c = Calendar.getInstance();
   return c.get(Calendar.YEAR)+"";
}

public String getMonth() {
   Calendar c = Calendar.getInstance();
   return c.get(Calendar.MONTH) + 1+"";
}

public String getDay() {
   Calendar c = Calendar.getInstance();
   return c.get(Calendar.DATE)+"";
}

public void initData() {
   setDate(getYear(), getMonth(), getDay());
   this.currentDay = 1+"";
   this.currentMonth = 1+"";
}

/**
 * 設定年月日
* 
 * @param year
* @param month
* @param day
*/
public void setDate(String year, String month, String day) {
   selectYear = year + "";
selectMonth = month + "";
selectDay = day + "";
issetdata = true;
   this.currentYear = year;
   this.currentMonth = month;
   this.currentDay = day;
   if (year == getYear()) {
      this.month = getMonth();
} else {
      this.month = 12+"";
}
   calDays(year, month);
}

/**
 * 設定年份
* 
 * @param year
*/
public int setYear(String year) {
   int yearIndex = 0;
   if (!year.equals(getYear())) {
      this.month = 12+"";
} else {
      this.month = getMonth();
}
   for (int i = Integer.parseInt(getYear()); i > 1950; i--) {
      if (i == Integer.parseInt(year)) {
         return yearIndex;
}
      yearIndex++;
}
   return yearIndex;
}

/**
 * 設定月份
* 
 * @param month
* @param month
* @return

            
           

相關推薦

開發-仿ios時間選擇控制元件timepicker

在開發中如果有地址或者日期選擇等就會涉及到時間或者條件選擇器,大家都會想到仿ios的三級聯動的效果,用wheelview實現,其實安卓原生自帶了時間和日期選擇器可能是效果來說太粗獷了,所以很多產品效果圖都是清一色的ios那種效果,ok,廢話說完了上圖 demo地址:htt

仿QQ時間選擇

###在軟體開發中我們會需要各種選擇選擇器,比如設定個人年齡時需要使用時間選擇器。設定地址需要用到地址選擇,還有身高等等。 ####下面介紹一下使用方法: 時間選擇器的使用: //這裡設定的是選擇之後可以回顯資料,所以先建立一個SP設定基本數值 Stri

自定義 日期和時間選擇器,在一個佈局中,可以直接呼叫

廢話不多說先看效果,效果不符合,就不要用看了。 安卓提供自己的日期選擇和時間選擇,但是樣式並不是自己想要的,如果非要把他們放在一起,會發現,樣式不好看,而且時間和日期選擇控制元件的大小不好控制,甚至根本沒法放在一行上,所以實現自定義日期時間選擇器有兩種方式 1.自己寫自定

自定義時間選擇控制元件仿ios滾動效果)

1.先上自定義的控制元件: /** * 滾輪選擇器 * author LH * data 2016/8/20 17:26 */ public class WheelView extends View { public static final String

開發(一)時間管理應用DayPlay

需求分析: 日常生活中,人們常常會因為拖拉而無法按時完成任務。“你有多少時間,就會花多少時間做一件事”,似乎是常人的通病。也許你開始時躊躇滿志,目標明確,可是時間一長往往就會偏離目標,將時間浪費在一些無謂的事情上。缺乏適時的、足夠多的提醒是主因。 當前,

開發仿微博自定義帶進度條和vip標識功能的圓形頭像IdentityImageView

*本篇文章已授權微信公眾號 guolin_blog(郭霖)獨家釋出 最近產品增加了兩個小功能,一個是頭像加一個進度條,用於升級提示,一個是身份標識功能,也就是標識Vip的功能,如圖: , 很多朋友看見這個小功能,肯定覺得特簡單,就是兩張

關於Delphi開發中主介面設計的一些元件的記錄

首先,這裡是大體的介面預覽。接下來,這裡是元件層次結構圖Form是最底層的圖層,在其中添加了5個Layout(佈局),Layout與Panel相比,Layout是沒有邊框線的,而Panel是具有實線邊框的。5個Layout從上至下的Align屬性分別為MostTop,top,

RN的高效能FlatList(相當於的RecycleView、iOS的TableView)元件的基本使用

電影列表 Item 先輸出Item元件 /** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React, {

自定義View進階-特殊控制元件的事件處理方案

本文帶大家瞭解 Android 特殊形狀控制元件的事件處理方式,主要是利用了 Region 和 Matrix 的一些方法,超級實用的事件處理方案,相信看完本篇之後,任何奇葩控制元件的事件處理都會變得十分簡單。 不得不說,Android 對事件體系封裝的非常棒,即便對事件體系不太

自定義Android日期時間選擇控制元件DateTimePickerDialog

ad = new AlertDialog.Builder(activity).setIcon(R.drawable.datetimeicon).setTitle(initDateTime).setView(dateTimeLayout).setPositiveButton("設定",

WinForm時間選擇控制元件(DateTimePicker)如何選擇(顯示)時分秒

WinForm時間選擇控制元件(DateTimePicker)如何選擇(顯示)時分秒 C# Windows窗體應用中,用到時間選擇控制元件DateTimePicker,發現不能選擇時分秒,難道要自己寫一個控制元件?! 答案是否定的,通過屬性修改是可以選擇時間的,DateTimePicker完

日期時間選擇控制元件

計算兩個日期和時間的間隔 在日期時間控制元件裡選擇控制元件事件 編輯函式 void Cdemo10Dlg::OnDtnDatetimechangeDatetimepicker1(NMHDR *pN

Android之日期時間選擇控制元件DatePicker和TimePicker

這個月根據需求在專案中做了一個時間選擇器,雖然沒有用到Android原生的時間選擇控制元件,但我羞愧地發現自己竟然從來沒有用過這方面控制元件!趁現在有時間,趕緊查缺補漏,寫一篇部落格吧。 (注:為了便於區分,本文將選擇年月日的控制元件稱為日期選擇控制元件,將選

ExtJs 基於ExtJs2.2以上的日期時間選擇控制元件datetimefield

自己收集了一個ExtJs的日期時間選擇空間,網上流傳了很多版本,自己做了一點改,我的3.4版本的專案中使用都還正常。 這個適用於2.0以上的版本。以下是原始碼。 Ext.ns('Ext.ux.form');    Ext.ux.form.TimePickerField

andriod studio 時間選擇控制元件的使用

首先是佈局檔案activity_main.xml<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.andr

EXT--日期時間選擇控制元件(精確到秒)

一。一些廢話近日來,有幾個專案用到了EXTJS作為Web前端。也看到了一些童鞋苦苦在網上尋覓可以選擇時間的控制元件,由於EXTJS版本差異較大,利用官方3.2的Demo製作了一個可以選擇到秒的時間控制元件,該控制元件只能夠用於ExtJs2.2以上的版本。經過測試ExtJs2.02版本不適用該控制元件,如

jquery時間選擇控制元件-laydate

檢視演示website立刻下載錯誤提交 填加用法 layDate致力於成為全球最用心的web日期支撐,為國內外所有從事web應用開發的同仁提供力所能及的動力。她基於原生JavaScript精心雕琢,相容了包括IE6在內的所有主流瀏覽器。她具備優雅的內部程式碼,良好的效能體驗

好用的js日期時間選擇控制元件

把日期時間控制元件包放到web目錄下,使用的頁面如下: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib p

pc/手機端時間選擇控制元件

pc端 <input class="Wdate" type="text" onClick="WdatePicker()"> <font color=red>&lt;- 點我彈出日期控制元件</font> <in

開發 頂部工具欄 帶返回功能 仿手機QQ頂部工具條

開發環境搭建   http://blog.csdn.net/juyangjia/article/details/9471561HelloWorld http://blog.csdn.net/juyangjia/article/details/9491781歡迎動畫製作&