該文章講解的源碼全憑個人積累的經驗編寫,所以如果有大神覺得不好或者有更好的意見希望可以提出來,謝謝!
本文的適讀人羣
- 從 zhongjhATC/AlbumCameraRecorder 過來了解如何更深入的自定義Camera佈局
- 想了解如何優化複雜UI的類
- 想了解如何封裝父類、抽象類作爲組件易於後續擴展
該源碼類是什麼功能,如何複雜法?
功能可以理解爲是微信的拍攝、錄製結合的界面,甚至支持多圖,多視頻錄製。具體功能可點擊zhongjhATC/AlbumCameraRecorder 查看。
那麼我們可以初步理解爲有以下幾個核心功能:
- 圖片功能
- 視頻功能
- 拍攝、錄製等功能
以上圖片功能和視頻功能又是隻通過一個按鈕觸發,那麼很多UI邏輯都會糅合在一個UI類裏面。所以,這次代碼優化選擇了狀態模式 + Facade模式。
同時,考慮到更多開發者使用該庫時會需要擴展不同的邏輯業務,所以,這些類都要弄成可繼承、可擴展使用的。
讓開發者更好了解如何使用,就出現了此文章。
我們先直接看優化後的CameraFragment如何擴展
- 自定義一個CameraFragment,需要繼承BaseCameraFragment,代碼如下:
public class CameraFragment1 extends BaseCameraFragment<CameraStateManagement, BaseCameraPicturePresenter, BaseCameraVideoPresenter> {
FragmentCamera1Binding mBinding;
BaseCameraPicturePresenter cameraPicturePresenter = new BaseCameraPicturePresenter(this);
BaseCameraVideoPresenter cameraVideoPresenter = new BaseCameraVideoPresenter(this);
CameraStateManagement cameraStateManagement = new CameraStateManagement(this);
public static CameraFragment1 newInstance() {
return new CameraFragment1();
}
@Override
public View setContentView(LayoutInflater inflater, ViewGroup container) {
mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_camera1,container,false);
return mBinding.getRoot();
}
@Override
public void initView(View view, Bundle savedInstanceState) {
}
/**
* TODO
* 覆寫該事件,賦值自定義按鈕事件
*/
@Override
protected void initListener() {
super.initListener();
mBinding.btnCustom.setOnClickListener(v -> Toast.makeText(getMyContext(), "我是自定義的", Toast.LENGTH_SHORT).show());
}
@NonNull
@Override
public IChildClickableLayout getChildClickableLayout() {
return mBinding.rlMain;
}
@Nullable
@Override
public View getTopView() {
return mBinding.clMenu;
}
@NonNull
@Override
public CameraView getCameraView() {
return mBinding.cameraView;
}
@Override
public RecyclerView getRecyclerViewPhoto() {
return mBinding.rlPhoto;
}
@Nullable
@Override
public View[] getMultiplePhotoView() {
return new View[]{mBinding.vLine1, mBinding.vLine2};
}
@NonNull
@Override
public PhotoVideoLayout getPhotoVideoLayout() {
return mBinding.pvLayout;
}
@NonNull
@Override
public com.zhongjh.albumcamerarecorder.widget.ImageViewTouch getSinglePhotoView() {
return mBinding.imgPhoto;
}
@Nullable
@Override
public View getCloseView() {
return mBinding.imgClose;
}
@Nullable
@Override
public ImageView getFlashView() {
return mBinding.imgFlash;
}
@Nullable
@Override
public ImageView getSwitchView() {
return mBinding.imgSwitch;
}
@NonNull
@Override
public CameraStateManagement getCameraStateManagement() {
return cameraStateManagement;
}
@NonNull
@Override
public BaseCameraPicturePresenter getCameraPicturePresenter() {
return cameraPicturePresenter;
}
@NonNull
@Override
public BaseCameraVideoPresenter getCameraVideoPresenter() {
return cameraVideoPresenter;
}
}
從上面代碼看到,我們提供一個View佈局(如果默認的就已經滿足了,就直接使用R.layout.fragment_camera_zjh
),然後在一些NonNull標記下必須提供不爲null的View或者類,接着就可以直接在自己的佈局上增加自己想要的View了,想擴展的地方可以覆寫方法添加邏輯。
當一個Fragment弄好後,我們需要告訴該庫使用該Fragment,代碼如下:
cameraSetting.setBaseCameraFragment(CameraFragment1.newInstance());
那麼在這裏就結束了,當然,如果你需要擴展更多的東西,我們需要更深入瞭解其他類的作用。讓我們繼續往下看
我們看看優化後的代碼包結構
camera -->
BaseCameraFragment
adapter-->
impl-->
presenter-->
PicturePresenter
VideoPresenter
state-->
StateInterface
StateMode
CameraStateManagement
type-->
PictureComplete
PictureMultiple
Preview
VideoComplete
VideoIn
VideoMultiple
VideoMultipleIn
以表格形式講解每一個包結構
包結構 | 作用 |
---|---|
BaseCameraFragment | 一個父類,提供於開發者繼承擴展該類,該類可認爲是個大容器,可使用其他類 |
adapter | 顧名思義,只是個adpater,目前只存放顯示多圖的適配器 |
impl | 包含所有接口,主要是用來告訴開發者都有哪些方法可以擴展使用 |
presenter | 並不是MVP的presenter,別混淆了。該presenter含有picture和video,picture是處理所有有關圖片UI的邏輯,而video是處理所有有關視頻UI的邏輯 |
state | 狀態模式,StateInterface告訴開發者狀態類有哪些方法可以擴展使用,StateMode是個實體,CameraStateManagement則是管理當前狀態,進行相關UI邏輯。而type裏面的PictureComplete、PictureMultiple…等則是各自狀態處理他們當前狀態的UI邏輯。 |
那麼瞭解過後,會發現,其實是三個實現類分解了這個UI類所包含的功能,腦圖:
接口
接口在這裏只起到規範性作用,單純是讓其他沒接觸過的開發者可以一目瞭然瞭解這些類有什麼方法。
這段代碼可以看到實現了兩個接口,區分兩個接口讓開發者更好分辨接口的方法屬於哪一類的
public abstract class BaseCameraFragment
extends BaseFragment implements ICameraView, ICameraFragment
以表格形式講解每一個接口類
接口 | 作用 | 鏈接 |
---|---|---|
ICameraFragment | 拍攝界面的接口,主要用於告示開發者可以使用哪些方法,大部分是關於View操作的界面邏輯,除了圖片、視頻、實例化View,其他方法都統一在Fragment使用 | ICameraFragment |
ICameraView | 錄製界面規定view的設置。對所有View都標記了NonNull和Nullable。標記了NonNull的View返回是不能爲空的,在佈局上必須使用這些View,當然,也可以繼承View加上你想要的方法 | ICameraView |
ICameraPicture | 拍攝界面的有關圖片View的接口,控制多圖Adapter也是在這裏實現 | ICameraPicture |
ICameraVideo | 拍攝界面的有關視頻View的接口 | ICameraVideo |
IState | 狀態事件接口,對於不同狀態下,他們各自的實現不一樣 | IState |
使用泛型+父類+抽象方法 規範開發者使用
從上面得知,我們CameraFragment封裝出了圖片、視頻、狀態三大類出去,那麼如何規定開發者必須引用這三大類進來呢?代碼如下:
public abstract class BaseCameraFragment
<StateManagement extends CameraStateManagement,
CameraPicture extends BaseCameraPicturePresenter,
CameraVideo extends BaseCameraVideoPresenter> {
@NonNull
public abstract StateManagement getCameraStateManagement();
@NonNull
public abstract CameraPicture getCameraPicturePresenter();
@NonNull
public abstract CameraVideo getCameraVideoPresenter();
}
那麼在子類繼承BaseCameraFragment得時候,必須實現這三個類
public class CameraFragment1 extends BaseCameraFragment<CameraStateManagement, BaseCameraPicturePresenter, BaseCameraVideoPresenter> {
BaseCameraPicturePresenter cameraPicturePresenter = new BaseCameraPicturePresenter(this);
@NonNull
@Override
public BaseCameraPicturePresenter getCameraPicturePresenter() {
return cameraPicturePresenter;
}
}
從上面三個實現方法得知,如果你想自定義有關圖片邏輯或者視頻邏輯,那麼需要繼承Presenter擴展你想要的方法後使用它,代碼如下:
public class CameraPicturePresenter extends BaseCameraPicturePresenter {
public CameraPicturePresenter(BaseCameraFragment<? extends CameraStateManagement, ? extends BaseCameraPicturePresenter, ? extends BaseCameraVideoPresenter> baseCameraFragment) {
super(baseCameraFragment);
}
@Override
public void takePhoto() {
super.takePhoto();
Toast.makeText(baseCameraFragment.getMyContext(), "拍照時觸發自定義事件!", Toast.LENGTH_SHORT).show();
}
}
開發者使用自定義CameraLayout過程碰到的疑問
-
1.我想動態加點數據,這些數據都是來自於上一個Activity
答:可以通過自定義的CameraFragment.setArguments()動態賦值,也可以使用event之類的,反正就當一個普通的fragment使用。
-
-
我想在關閉前做些事 爲什麼設置完監聽 關閉不掉了mBinding.imgClose.setOnClickListener(v -> { // 自定義代碼 })
答:因爲你關閉事件覆蓋掉了,要熟悉基類的本身事件。正確做法是使用基類的有關關閉事件方法,再加上你的自定義事件。(多瞭解基類)
-
寫在最後
那麼基本在這裏就差不多介紹結束了,想了解更加具體的做法和源碼的,請下載下面的GitHub源碼鏈接